diff --git a/banque.c b/banque.c index e4783a6..6126447 100644 --- a/banque.c +++ b/banque.c @@ -1,332 +1,332 @@ -/* - * R5.A.04 - Qualité Algorithmique - * TD Complexité algorithmique : analyse des transactions d'une banque - * - * Le programme génère des transactions pseudo-aléatoires (toujours les - * mêmes, grâce à une graine fixe), puis calcule plusieurs indicateurs - * et produit un rapport dans rapport.txt. - * - * L'empreinte affichée à la fin doit rester IDENTIQUE après vos - * optimisations : elle prouve que le résultat n'a pas changé. - * - * Interdit : modifier les constantes, la génération des données, - * le format du rapport, ou écrire les résultats « en dur ». - * - * Compilation : gcc -O2 banque.c -o banque - * Profiling : gcc -O0 -g -pg banque.c -o banque && ./banque && gprof ./banque gmon.out - */ - -#include -#include - -#define NB_TRANSACTIONS 30000 -#define NB_CLIENTS 2000 -#define NB_JOURS 1000 -#define NB_REQUETES 30000 -#define MONTANT_MONNAIE 400 -#define GRAINE 20261020UL - -typedef struct { - int id; - int client; - int jour; - long montant; /* en centimes, négatif pour un débit */ -} Transaction; - -/* ------------------------------------------------------------------ */ -/* Données (ne pas modifier) */ -/* ------------------------------------------------------------------ */ - -static unsigned long etat_alea = GRAINE; - -unsigned long alea(void) -{ - etat_alea = etat_alea * 6364136223846793005UL + 1442695040888963407UL; - return etat_alea >> 33; -} - -Transaction *generer_transactions(int n) -{ - Transaction *t = malloc(n * sizeof(Transaction)); - for (int i = 0; i < n; i++) { - t[i].id = i; - t[i].client = alea() % NB_CLIENTS; - t[i].jour = alea() % NB_JOURS; - if (i > 0 && alea() % 20 == 0) - t[i].montant = -t[alea() % i].montant; /* opération inverse */ - else - t[i].montant = (long)(alea() % 100001) - 50000; - } - for (int i = n - 1; i > 0; i--) { /* mélange des id */ - int j = alea() % (i + 1); - int tmp = t[i].id; - t[i].id = t[j].id; - t[j].id = tmp; - } - return t; -} - -/* ------------------------------------------------------------------ */ -/* Fonctions à analyser et à optimiser */ -/* ------------------------------------------------------------------ */ - -/* F1 : trie les transactions par montant croissant, puis par id */ -void trier_par_montant(Transaction *t, int n) -{ - for (int i = 1; i < n; i++) { - Transaction cle = t[i]; - int j = i - 1; - while (j >= 0 && (t[j].montant > cle.montant || - (t[j].montant == cle.montant && t[j].id > cle.id))) { - t[j + 1] = t[j]; - j--; - } - t[j + 1] = cle; - } -} - -/* F2 : nombre de clients ayant au moins une transaction */ -int nb_clients_distincts(const Transaction *t, int n) -{ - int nb = 0; - for (int i = 0; i < n; i++) { - int deja_vu = 0; - for (int j = 0; j < i; j++) { - if (t[j].client == t[i].client) { - deja_vu = 1; - break; - } - } - if (!deja_vu) - nb++; - } - return nb; -} - -/* F3 : solde total de chaque client */ -void total_par_client(const Transaction *t, int n, long *totaux) -{ - for (int c = 0; c < NB_CLIENTS; c++) { - totaux[c] = 0; - for (int i = 0; i < n; i++) - if (t[i].client == c) - totaux[c] += t[i].montant; - } -} - -/* F4 : solde total de chaque jour */ -void total_par_jour(const Transaction *t, int n, long *totaux) -{ - for (int j = 0; j < NB_JOURS; j++) { - totaux[j] = 0; - for (int i = 0; i < n; i++) - if (t[i].jour == j) - totaux[j] += t[i].montant; - } -} - -/* F5 : période de jours consécutifs dont la somme des soldes est maximale */ -long meilleure_periode(const long *totaux, int nb_jours, int *debut, int *fin) -{ - long meilleur = totaux[0]; - *debut = 0; - *fin = 0; - for (int i = 0; i < nb_jours; i++) { - for (int j = i; j < nb_jours; j++) { - long somme = 0; - for (int k = i; k <= j; k++) - somme += totaux[k]; - if (somme > meilleur) { - meilleur = somme; - *debut = i; - *fin = j; - } - } - } - return meilleur; -} - -/* F6 : retrouve une transaction à partir de son id */ -const Transaction *rechercher_par_id(const Transaction *t, int n, int id) -{ - for (int i = 0; i < n; i++) - if (t[i].id == id) - return &t[i]; - return NULL; -} - -/* F7 : nombre de paires de transactions de montants opposés (a + b = 0) */ -long nb_paires_opposees(const Transaction *t, int n) -{ - long nb = 0; - for (int i = 0; i < n; i++) - for (int j = i + 1; j < n; j++) - if (t[i].montant + t[j].montant == 0) - nb++; - return nb; -} - -/* F8 : nombre de façons de rendre une somme avec les pièces disponibles */ -static const int PIECES[] = {1, 2, 5, 10, 20, 50, 100, 200}; -#define NB_PIECES 8 - -long nb_facons_rendre(int somme, int nb_pieces) -{ - if (somme == 0) - return 1; - if (somme < 0 || nb_pieces == 0) - return 0; - return nb_facons_rendre(somme - PIECES[nb_pieces - 1], nb_pieces) + - nb_facons_rendre(somme, nb_pieces - 1); -} - -/* F9 : catégorie d'une transaction (pour les statistiques) */ -int categorie(const Transaction *t) -{ - if (t->montant < 0) { - if (t->montant < -20000) { - if (t->jour % 7 == 5 || t->jour % 7 == 6) - return 1; - else - return 2; - } else if (t->montant < -5000) { - return 3; - } else { - if (t->client % 10 == 0) - return 4; - return 5; - } - } else if (t->montant == 0) { - return 0; - } else { - if (t->montant > 30000) { - if (t->client % 10 == 0 && t->jour % 7 < 5) - return 6; - return 7; - } else if (t->montant > 10000) { - return 8; - } else if (t->montant > 1000) { - if (t->jour % 30 == 0) - return 9; - return 10; - } - return 11; - } -} - -/* F10 : ajoute une ligne à la fin du rapport */ -int longueur(const char *s) -{ - int n = 0; - while (s[n] != '\0') - n++; - return n; -} - -void ajouter_ligne(char *rapport, const char *ligne) -{ - int fin = longueur(rapport); - int i = 0; - while (ligne[i] != '\0') { - rapport[fin + i] = ligne[i]; - i++; - } - rapport[fin + i] = '\0'; -} - -/* ------------------------------------------------------------------ */ -/* Programme principal (à adapter si vous changez les signatures) */ -/* ------------------------------------------------------------------ */ - -unsigned long empreinte(const char *s) -{ - unsigned long h = 1469598103934665603UL; - for (int i = 0; s[i] != '\0'; i++) { - h ^= (unsigned char)s[i]; - h *= 1099511628211UL; - } - return h; -} - -int main(void) -{ - Transaction *t = generer_transactions(NB_TRANSACTIONS); - char *rapport = malloc(1 << 20); - char ligne[128]; - rapport[0] = '\0'; - - /* recherches par id, dans l'ordre d'origine */ - long somme_requetes = 0; - unsigned long etat = 12345; - for (int q = 0; q < NB_REQUETES; q++) { - etat = etat * 1103515245UL + 12345UL; - const Transaction *r = rechercher_par_id(t, NB_TRANSACTIONS, (int)((etat >> 8) % NB_TRANSACTIONS)); - if (r != NULL) - somme_requetes += r->montant; - } - - int nb_clients = nb_clients_distincts(t, NB_TRANSACTIONS); - - static long totaux_clients[NB_CLIENTS]; - total_par_client(t, NB_TRANSACTIONS, totaux_clients); - - static long totaux_jours[NB_JOURS]; - total_par_jour(t, NB_TRANSACTIONS, totaux_jours); - int debut, fin; - long meilleure = meilleure_periode(totaux_jours, NB_JOURS, &debut, &fin); - - long nb_paires = nb_paires_opposees(t, NB_TRANSACTIONS); - - int categories[12] = {0}; - for (int i = 0; i < NB_TRANSACTIONS; i++) - categories[categorie(&t[i])]++; - - trier_par_montant(t, NB_TRANSACTIONS); - - long nb_facons = nb_facons_rendre(MONTANT_MONNAIE, NB_PIECES); - - /* rapport */ - snprintf(ligne, sizeof(ligne), "clients distincts;%d\n", nb_clients); - ajouter_ligne(rapport, ligne); - snprintf(ligne, sizeof(ligne), "somme des requetes;%ld\n", somme_requetes); - ajouter_ligne(rapport, ligne); - snprintf(ligne, sizeof(ligne), "meilleure periode;%d;%d;%ld\n", debut, fin, meilleure); - ajouter_ligne(rapport, ligne); - snprintf(ligne, sizeof(ligne), "paires opposees;%ld\n", nb_paires); - ajouter_ligne(rapport, ligne); - snprintf(ligne, sizeof(ligne), "facons de rendre %d;%ld\n", MONTANT_MONNAIE, nb_facons); - ajouter_ligne(rapport, ligne); - for (int c = 0; c < 12; c++) { - snprintf(ligne, sizeof(ligne), "categorie;%d;%d\n", c, categories[c]); - ajouter_ligne(rapport, ligne); - } - for (int c = 0; c < NB_CLIENTS; c++) { - snprintf(ligne, sizeof(ligne), "client;%d;%ld\n", c, totaux_clients[c]); - ajouter_ligne(rapport, ligne); - } - for (int j = 0; j < NB_JOURS; j++) { - snprintf(ligne, sizeof(ligne), "jour;%d;%ld\n", j, totaux_jours[j]); - ajouter_ligne(rapport, ligne); - } - for (int i = 0; i < NB_TRANSACTIONS; i += 10) { - snprintf(ligne, sizeof(ligne), "rang;%d;%d;%ld\n", i, t[i].id, t[i].montant); - ajouter_ligne(rapport, ligne); - } - - FILE *f = fopen("rapport.txt", "w"); - if (f != NULL) { - fputs(rapport, f); - fclose(f); - } - - printf("Clients distincts : %d\n", nb_clients); - printf("Somme des requêtes : %ld\n", somme_requetes); - printf("Meilleure période : jours %d à %d (%ld)\n", debut, fin, meilleure); - printf("Paires opposées : %ld\n", nb_paires); - printf("Façons de rendre %d : %ld\n", MONTANT_MONNAIE, nb_facons); - printf("Empreinte : %016lx\n", empreinte(rapport)); - - free(rapport); - free(t); - return 0; -} +/* + * R5.A.04 - Qualité Algorithmique + * TD Complexité algorithmique : analyse des transactions d'une banque + * + * Le programme génère des transactions pseudo-aléatoires (toujours les + * mêmes, grâce à une graine fixe), puis calcule plusieurs indicateurs + * et produit un rapport dans rapport.txt. + * + * L'empreinte affichée à la fin doit rester IDENTIQUE après vos + * optimisations : elle prouve que le résultat n'a pas changé. + * + * Interdit : modifier les constantes, la génération des données, + * le format du rapport, ou écrire les résultats « en dur ». + * + * Compilation : gcc -O2 banque.c -o banque + * Profiling : gcc -O0 -g -pg banque.c -o banque && ./banque && gprof ./banque gmon.out + */ + +#include +#include + +#define NB_TRANSACTIONS 30000 +#define NB_CLIENTS 2000 +#define NB_JOURS 1000 +#define NB_REQUETES 30000 +#define MONTANT_MONNAIE 400 +#define GRAINE 20261020UL + +typedef struct { + int id; + int client; + int jour; + long montant; /* en centimes, négatif pour un débit */ +} Transaction; + +/* ------------------------------------------------------------------ */ +/* Données (ne pas modifier) */ +/* ------------------------------------------------------------------ */ + +static unsigned long etat_alea = GRAINE; + +unsigned long alea(void) +{ + etat_alea = etat_alea * 6364136223846793005UL + 1442695040888963407UL; + return etat_alea >> 33; +} + +Transaction *generer_transactions(int n) +{ + Transaction *t = malloc(n * sizeof(Transaction)); + for (int i = 0; i < n; i++) { + t[i].id = i; + t[i].client = alea() % NB_CLIENTS; + t[i].jour = alea() % NB_JOURS; + if (i > 0 && alea() % 20 == 0) + t[i].montant = -t[alea() % i].montant; /* opération inverse */ + else + t[i].montant = (long)(alea() % 100001) - 50000; + } + for (int i = n - 1; i > 0; i--) { /* mélange des id */ + int j = alea() % (i + 1); + int tmp = t[i].id; + t[i].id = t[j].id; + t[j].id = tmp; + } + return t; +} + +/* ------------------------------------------------------------------ */ +/* Fonctions à analyser et à optimiser */ +/* ------------------------------------------------------------------ */ + +/* F1 : trie les transactions par montant croissant, puis par id */ +void trier_par_montant(Transaction *t, int n) +{ + for (int i = 1; i < n; i++) { + Transaction cle = t[i]; + int j = i - 1; + while (j >= 0 && (t[j].montant > cle.montant || + (t[j].montant == cle.montant && t[j].id > cle.id))) { + t[j + 1] = t[j]; + j--; + } + t[j + 1] = cle; + } +} + +/* F2 : nombre de clients ayant au moins une transaction */ +int nb_clients_distincts(const Transaction *t, int n) +{ + int nb = 0; + for (int i = 0; i < n; i++) { + int deja_vu = 0; + for (int j = 0; j < i; j++) { + if (t[j].client == t[i].client) { + deja_vu = 1; + break; + } + } + if (!deja_vu) + nb++; + } + return nb; +} + +/* F3 : solde total de chaque client en O(n + c) */ +void total_par_client(const Transaction *t, int n, long *totaux) +{ + for (int c = 0; c < NB_CLIENTS; c++) { + totaux[c] = 0; + } + for (int i = 0; i < n; i++) { + totaux[t[i].client] += t[i].montant; + } +} + +/* F4 : solde total de chaque jour */ +void total_par_jour(const Transaction *t, int n, long *totaux) +{ + for (int j = 0; j < NB_JOURS; j++) { + totaux[j] = 0; + for (int i = 0; i < n; i++) + if (t[i].jour == j) + totaux[j] += t[i].montant; + } +} + +/* F5 : période de jours consécutifs dont la somme des soldes est maximale */ +long meilleure_periode(const long *totaux, int nb_jours, int *debut, int *fin) +{ + long meilleur = totaux[0]; + *debut = 0; + *fin = 0; + for (int i = 0; i < nb_jours; i++) { + for (int j = i; j < nb_jours; j++) { + long somme = 0; + for (int k = i; k <= j; k++) + somme += totaux[k]; + if (somme > meilleur) { + meilleur = somme; + *debut = i; + *fin = j; + } + } + } + return meilleur; +} + +/* F6 : retrouve une transaction à partir de son id */ +const Transaction *rechercher_par_id(const Transaction *t, int n, int id) +{ + for (int i = 0; i < n; i++) + if (t[i].id == id) + return &t[i]; + return NULL; +} + +/* F7 : nombre de paires de transactions de montants opposés (a + b = 0) */ +long nb_paires_opposees(const Transaction *t, int n) +{ + long nb = 0; + for (int i = 0; i < n; i++) + for (int j = i + 1; j < n; j++) + if (t[i].montant + t[j].montant == 0) + nb++; + return nb; +} + +/* F8 : nombre de façons de rendre une somme avec les pièces disponibles */ +static const int PIECES[] = {1, 2, 5, 10, 20, 50, 100, 200}; +#define NB_PIECES 8 + +long nb_facons_rendre(int somme, int nb_pieces) +{ + if (somme == 0) + return 1; + if (somme < 0 || nb_pieces == 0) + return 0; + return nb_facons_rendre(somme - PIECES[nb_pieces - 1], nb_pieces) + + nb_facons_rendre(somme, nb_pieces - 1); +} + +/* F9 : catégorie d'une transaction (pour les statistiques) */ +int categorie(const Transaction *t) +{ + if (t->montant < 0) { + if (t->montant < -20000) { + if (t->jour % 7 == 5 || t->jour % 7 == 6) + return 1; + else + return 2; + } else if (t->montant < -5000) { + return 3; + } else { + if (t->client % 10 == 0) + return 4; + return 5; + } + } else if (t->montant == 0) { + return 0; + } else { + if (t->montant > 30000) { + if (t->client % 10 == 0 && t->jour % 7 < 5) + return 6; + return 7; + } else if (t->montant > 10000) { + return 8; + } else if (t->montant > 1000) { + if (t->jour % 30 == 0) + return 9; + return 10; + } + return 11; + } +} + +/* F10 : ajoute une ligne à la fin du rapport */ +int longueur(const char *s) +{ + int n = 0; + while (s[n] != '\0') + n++; + return n; +} + +void ajouter_ligne(char *rapport, const char *ligne) +{ + int fin = longueur(rapport); + int i = 0; + while (ligne[i] != '\0') { + rapport[fin + i] = ligne[i]; + i++; + } + rapport[fin + i] = '\0'; +} + +/* ------------------------------------------------------------------ */ +/* Programme principal (à adapter si vous changez les signatures) */ +/* ------------------------------------------------------------------ */ + +unsigned long empreinte(const char *s) +{ + unsigned long h = 1469598103934665603UL; + for (int i = 0; s[i] != '\0'; i++) { + h ^= (unsigned char)s[i]; + h *= 1099511628211UL; + } + return h; +} + +int main(void) +{ + Transaction *t = generer_transactions(NB_TRANSACTIONS); + char *rapport = malloc(1 << 20); + char ligne[128]; + rapport[0] = '\0'; + + /* recherches par id, dans l'ordre d'origine */ + long somme_requetes = 0; + unsigned long etat = 12345; + for (int q = 0; q < NB_REQUETES; q++) { + etat = etat * 1103515245UL + 12345UL; + const Transaction *r = rechercher_par_id(t, NB_TRANSACTIONS, (int)((etat >> 8) % NB_TRANSACTIONS)); + if (r != NULL) + somme_requetes += r->montant; + } + + int nb_clients = nb_clients_distincts(t, NB_TRANSACTIONS); + + static long totaux_clients[NB_CLIENTS]; + total_par_client(t, NB_TRANSACTIONS, totaux_clients); + + static long totaux_jours[NB_JOURS]; + total_par_jour(t, NB_TRANSACTIONS, totaux_jours); + int debut, fin; + long meilleure = meilleure_periode(totaux_jours, NB_JOURS, &debut, &fin); + + long nb_paires = nb_paires_opposees(t, NB_TRANSACTIONS); + + int categories[12] = {0}; + for (int i = 0; i < NB_TRANSACTIONS; i++) + categories[categorie(&t[i])]++; + + trier_par_montant(t, NB_TRANSACTIONS); + + long nb_facons = nb_facons_rendre(MONTANT_MONNAIE, NB_PIECES); + + /* rapport */ + snprintf(ligne, sizeof(ligne), "clients distincts;%d\n", nb_clients); + ajouter_ligne(rapport, ligne); + snprintf(ligne, sizeof(ligne), "somme des requetes;%ld\n", somme_requetes); + ajouter_ligne(rapport, ligne); + snprintf(ligne, sizeof(ligne), "meilleure periode;%d;%d;%ld\n", debut, fin, meilleure); + ajouter_ligne(rapport, ligne); + snprintf(ligne, sizeof(ligne), "paires opposees;%ld\n", nb_paires); + ajouter_ligne(rapport, ligne); + snprintf(ligne, sizeof(ligne), "facons de rendre %d;%ld\n", MONTANT_MONNAIE, nb_facons); + ajouter_ligne(rapport, ligne); + for (int c = 0; c < 12; c++) { + snprintf(ligne, sizeof(ligne), "categorie;%d;%d\n", c, categories[c]); + ajouter_ligne(rapport, ligne); + } + for (int c = 0; c < NB_CLIENTS; c++) { + snprintf(ligne, sizeof(ligne), "client;%d;%ld\n", c, totaux_clients[c]); + ajouter_ligne(rapport, ligne); + } + for (int j = 0; j < NB_JOURS; j++) { + snprintf(ligne, sizeof(ligne), "jour;%d;%ld\n", j, totaux_jours[j]); + ajouter_ligne(rapport, ligne); + } + for (int i = 0; i < NB_TRANSACTIONS; i += 10) { + snprintf(ligne, sizeof(ligne), "rang;%d;%d;%ld\n", i, t[i].id, t[i].montant); + ajouter_ligne(rapport, ligne); + } + + FILE *f = fopen("rapport.txt", "w"); + if (f != NULL) { + fputs(rapport, f); + fclose(f); + } + + printf("Clients distincts : %d\n", nb_clients); + printf("Somme des requêtes : %ld\n", somme_requetes); + printf("Meilleure période : jours %d à %d (%ld)\n", debut, fin, meilleure); + printf("Paires opposées : %ld\n", nb_paires); + printf("Façons de rendre %d : %ld\n", MONTANT_MONNAIE, nb_facons); + printf("Empreinte : %016lx\n", empreinte(rapport)); + + free(rapport); + free(t); + return 0; +}