#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);
}