correction-5.c

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

/**
 * Pour cet exercice, il vous est fortement consille de prendre un papier et un stylo.
 * Je vous conseille aussi fortement de brancher votre cerveau, de prendre du temps,
 * la complexite de cet exercice est nettement plus elevee que pour les exo precedents.
 * Ceci ne regarde que vous, faites le serieusement, si vous y arrivez,
 * vous etes pret pour le partiel !
 *
 * Note: pour s'en sortir, relisez vos cours d'algo sur les listes.
 **/

#define MAX_CONTACT 200
#define MAX_STRING 64
#define MAX_TEL 12

enum genre {
    ORGANISATION, HOMME, FEMME
};

struct contact {
    int id;
    enum genre sexe;
    char lastname[MAX_STRING + 1]; // + 1 pour le zero terminal
    char firstname[MAX_STRING + 1];
    char tel[MAX_TEL + 1]; // FIXME remarque c'est un poil plus grand qu'un numero francais,
    // et ce sont des chars et non des nombres pour stocker les caracteres tels que +, * ou #
    char addr[MAX_STRING + 1]; // TODO rajouter la ville, le code postal et autres.
};

struct lst_contact {
    struct contact contact;
    struct lst_contact* next;
};

struct book {
    int next_id;
    struct lst_contact *used, *free;
    struct lst_contact pool[MAX_CONTACT];
};

int member(int n, char* tab[], char* s)
{
    for (int i = 0; i < n; i++) {
        if (!strcasecmp(s, tab[i]))  // On a pas vu la fonction en cours, c'est comme `strcmp`
                                     // a la casse près (majuscule/minuscule)
            return i;
    }
    return -1;
}

void scan_string(int size, char* buffer)
{
    /*
     * ou pourrait utiliser `scanf("%s", ...)` mais il ne tolere pas franchement les espaces
     * ce qui n'est pas super pratique pour les addresses.
     *
     * du coup, on voudrait utiliser `gets`, cependant la doc (`man gets`) dit :
     * the gets() function cannot be used securely. [...]
     * it is strongly suggested that the fgets() function be used in all cases.
     *
     * vous pouvez donc utiliser/retenir l'appel suivant comme un idiome !
     */
    //      buffer[size] = 0; // On rajoute le zero terminal a la fin (juste au cas ou)
    //      fgets(buffer, size, stdin);
    //      // Dans certain cas (cf. la doc) fgets rajoute un saut de ligne
    //      // On enleve donc ce saut de ligne
    //      int i = strrchr(buffer, '\n');
    //      if (i >= 0)
    //              buffer[i] = 0;

    /*
     * Un autre solution probablement plus claire, consiste a reecrire (f)gets.
     */
    int i = 0; // compteur de caracteres
    int c; // pour stocker le prochain caractere
    while ((c = getchar()) > 0 && c != '\n' && i < size) // Tant qu'on a des caracteres a lire
                                                        // et que ce n'est pas entree
                                                        // et qu'on a pas depase la taille max
        buffer[i++] = c;
    buffer[i] = 0; // et on rajoute le zero terminal

    // On pourrait s'arreter la, cependant si l'utilisateur tape trop de
    // caracteres ils vont trainer et parasiter la prochaine lecture. Pour etre
    // complet et correct il faut donc les manger.
    while (i < size && (c = getchar()) > 0)
        ; // On ne fait rien
}

enum genre scan_genre()
{
    char tmp[13]; // Taille de organisation + 1
    char* genres[] = { "ORGANISATION", "HOMME", "FEMME" };
    int g = ORGANISATION; // On stocke un element d'union dans un entier ... et ca marche.
    printf("Genre (organisation, homme, femme): ");
    scan_string(12, tmp); // La longeur d'organisation
    g = member(3, genres, tmp);
    if (g == -1) // si le genre n'est pas trouve, on utilisera organisation
        g = ORGANISATION; // ceci illustre encore qu'une enum n'est rien de plus qu'un nombre
    return g; // Remarquer que l'entier est automatiquement converti dans l'union ... et ca marche.
}

struct contact scan_contact()
{
    struct contact c;
    c.sexe = scan_genre();
    printf("Nom: ");
    scan_string(MAX_STRING, c.lastname);
    if (c.sexe != ORGANISATION) {
        printf("prenom: ");
        scan_string(MAX_STRING, c.firstname);
    }
    printf("Adresse: ");
    scan_string(MAX_STRING, c.addr);

    printf("tel: ");
    scan_string(MAX_TEL, c.tel);

    return c;
}

void print_contact_name(struct contact* c)
{
    char* genres[] = { "M.", "Mme" };
    if (c->sexe == ORGANISATION)
        printf("%s", c->lastname);
    else
        printf("%s %s %s", genres[c->sexe - 1], c->firstname, c->lastname);
}

void print_contact_short(struct contact* c)
{
    print_contact_name(c);
    printf(": %s\n", c->tel);
}

void print_contact(struct contact* c)
{
    print_contact_name(c);
    printf("\n%s\ntel: %s\n", c->addr, c->tel);
}

void print_address_book(struct book* ab)
{
    struct lst_contact* c = ab->used;
    while (c) { // c != NULL
        printf("[%3d] ", c->contact.id);
        print_contact_short(&c->contact);
        c = c->next;
    }
}

void init_address_book(struct book* ab)
{
    ab->next_id = 0;
    ab->used = NULL;
    ab->free = ab->pool; // ab->pool == &(ab->pool + 0) == &(ab->pool[0])
    ab->pool[MAX_CONTACT - 1].next = NULL;

    for (int i = 1; i < MAX_CONTACT; i++)
        ab->pool[i - 1].next = &ab->pool[i];
}

struct lst_contact* get_free_slot(struct book* ab)
{
    struct lst_contact* res = ab->free; // le premier contact libre est celui directement pointe par la case `free`
    if (res != NULL) // S'il existe un slot disponible, le sortir de la liste
        ab->free = res->next; // c'est a dire, prendre le suivant
    return res;
}

void add_used_slot(struct book* ab, struct lst_contact* item)
{
    item->next = ab->used; // rajouter le nouveau devant l'ancien premier
    ab->used = item; // maintenant le nouveau premier est le courant.
}

// Peut on remplacer ce contact par un pointeur ? Si oui le faire.
// Vous poser la question pour toutes les fonctions (precedentes ou suivantes):
// "Peut on enlever/rajouter un pointeur."
void add_contact(struct contact c, struct book* ab)
{
    struct lst_contact* n = get_free_slot(ab);
    if (n == NULL) {
        printf("plus de place pour stocker votre contact.\n");
        return; // eventuellement exit(EXIT_FAILURE);
    }
    n->contact = c;
    n->contact.id = ab->next_id++; // Set Id of current contact and increment next id
    add_used_slot(ab, n);
}

struct contact* find_contact_by_name(char* name, struct book* ab)
{
    struct lst_contact* c = ab->used;
    while (c) { // i.e., c != NULL
        if (!strcasecmp(c->contact.lastname, name))
            return &c->contact;
        c = c->next;
    }
    return NULL;
}

void print_contact_by_name(char* name, struct book* ab)
{
    struct contact* c = find_contact_by_name(name, ab);
    if (c == NULL)
        printf("Contact '%s' inexistant\n", name);
    else
        print_contact(c);
}

struct contact* find_contact_by_id(int id, struct book* ab)
{
    struct lst_contact* c = ab->used;
    while (c) { // i.e., c != NULL
        if (c->contact.id == id)
            return &c->contact;
        c = c->next;
    }
    return NULL;
}


// La question suivante est pour vous, vous pouvez ecrire la fonction `start_with`
// qui compare les `n` premiers caracteres ... ou lire la doc de `strcmp` (et reflechir qques secondes).


/**
 * Cette fonction retire le contact qui a l'id `id` du carnet d'adresse.
 */
void remove_contact(int id, struct book* ab)
{
    // Cette fonction est relativement compliqué ...  en effet pour 'retirer' un
    // element d'une liste simplement chainee il faut connaitre le maillon
    // precedent !  Comme si cela ne suiffisait pas, ATTENTION au cas particulier
    // du premier maillon.
    //
    // En question subsidiaire, comment pourriez vous separer cette fonction en
    // `sous-fonctions` plus utiles.  Comment unifier ces sous-fonctions avec
    // celles pour `ajouter` un contact.

    struct lst_contact* prev = NULL;
    struct lst_contact* item = ab->used;
    while (item) { // i.e., item != NULL
        if (item->contact.id == id)
            break; // Une fois n'est pas coutume on sort de la boucle de facon un poil premature 
        prev = item;
        item = item->next;
    }
    if (item == NULL) return; // Le contact n'est pas trouve
    if (prev == NULL) { // c'est le cas particulier du premier maillon
        ab->used = item->next;
    } else { // sinon le maillon precedent a comme suivant, le maillon suivant du maillon trouve
             // (ca parait complique a lire, mais dessinez le !).
        prev->next = item->next;
    }
    // Maintenant on racroche le maillon aux contacts libres.
    item->next = ab->free;
    ab->free = item;
}

// J'arrete la correction ici puisqu'il n'y a plus rien de vraiment complique.
// Ceci etant dit, j'insiste, faites les, histoire de verifier que vous avez
// compris.
//
//
// Rajouter des entrees au pool, represente une ``petite'' difficulte.
// Globalement l'idee est de remplacer dans la structures le tableau par un
// ptr.  Cependant, rappellez vous, un ptr et un tableau c'est pareil pour les
// types ...  mais pas pour la memoire ! Il vous faudra donc ``passer'' un
// tableau vide lors de l'initialisation.  Pour le faire de facon automatique,
// la seule solution c'est `malloc` et `free` (et le debut des ennuis).  La
// strategie la plus courrament utilise est la suivante, quand le nb max
// d'entree est atteint, on alloue un tableau du double de la taille du tableau
// courrant. Puis on recopie les l'ancien dans le nouveau (`memcpy` fait
// parfaitement l'affaire). On ne reduit jamais la taille du tableau !  Ceci
// peut paraitre etrange, l'idee sous-jacente est la suivante : si on a eu un
// jour besoin de ces elements, on en aura probablement besoin a nouveau.
// (Pour les plus braves, ou les cas ou cela est necessaire, on peut quand meme
// faire une fonction 'compact' qui reduit la taille du tableau exactement au
// nombre d'entree utiles.)

/**
 * Ce main est completement stupide mais permet de tester vite fait.
 * Ameliorez le !
 */
int main(int argc, char* argv[])
{
    struct book ab;
    init_address_book(&ab);

    for (int i = 0; i < 3; i++) {
        struct contact c = scan_contact();
        add_contact(c, &ab);
    }

    print_address_book(&ab);
    print_contact_by_name("foo", &ab);

    remove_contact(1, &ab);
    print_address_book(&ab);
    remove_contact(2, &ab);
    print_address_book(&ab);
    remove_contact(0, &ab);
    print_address_book(&ab);
}