/* * 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. * Les id sont tous distincts : l'ordre est total, donc qsort (non stable) * donne le même résultat que le tri à bulles d'origine. */ int comparer_transactions(const void *pa, const void *pb) { const Transaction *a = pa, *b = pb; if (a->montant != b->montant) return (a->montant > b->montant) - (a->montant < b->montant); return (a->id > b->id) - (a->id < b->id); } void trier_par_montant(Transaction *t, int n) { qsort(t, n, sizeof(Transaction), comparer_transactions); } /* 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 */ /* Avec prefixe(j) = totaux[0] + ... + totaux[j-1], la somme des jours i à j * vaut prefixe(j+1) - prefixe(i). Pour chaque fin j, le meilleur début est * donc l'indice du plus petit prefixe(i), i <= j. On garde le premier minimum * et on ne remplace le meilleur que s'il est strictement dépassé : on obtient * la même période que la triple boucle d'origine, même en cas d'égalité. */ long meilleure_periode(const long *totaux, int nb_jours, int *debut, int *fin) { long meilleur = totaux[0]; long prefixe = 0, prefixe_min = 0; int debut_min = 0; *debut = 0; *fin = 0; for (int j = 0; j < nb_jours; j++) { if (prefixe < prefixe_min) { /* prefixe vaut ici prefixe(j) */ prefixe_min = prefixe; debut_min = j; } prefixe += totaux[j]; /* prefixe vaut maintenant prefixe(j+1) */ if (prefixe - prefixe_min > meilleur) { meilleur = prefixe - prefixe_min; *debut = debut_min; *fin = j; } } return meilleur; } /* F6 : position[id] = indice de la transaction id dans t. Les id forment une * permutation de 0 .. n-1 (voir generer_transactions) : chaque id a sa case. */ int *indexer_par_id(const Transaction *t, int n) { int *position = malloc(n * sizeof(int)); for (int i = 0; i < n; i++) position[t[i].id] = i; return position; } /* F6 : retrouve une transaction à partir de son id, en O(1) grâce à l'index */ const Transaction *rechercher_par_id(const Transaction *t, const int *position, int id) { return &t[position[id]]; } /* Premier indice i tel que t[i].montant >= v, dans t trié par montant * croissant (recherche dichotomique, O(log n)). */ int premier_indice(const Transaction *t, int n, long v) { int debut = 0, fin = n; while (debut < fin) { int milieu = (debut + fin) / 2; if (t[milieu].montant < v) debut = milieu + 1; else fin = milieu; } return debut; } /* Nombre de transactions de montant v, dans t trié par montant croissant */ int effectif(const Transaction *t, int n, long v) { return premier_indice(t, n, v + 1) - premier_indice(t, n, v); } /* F7 : nombre de paires de transactions de montants opposés (a + b = 0). * t doit être trié par montant (F1). On compte, pour chaque transaction, les * transactions de montant opposé : chaque paire est vue deux fois, et chaque * montant nul se compte lui-même une fois en trop. */ long nb_paires_opposees(const Transaction *t, int n) { long nb = 0; for (int i = 0; i < n; i++) nb += effectif(t, n, -t[i].montant); return (nb - effectif(t, n, 0)) / 2; } /* 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 /* Programmation dynamique : après avoir traité les p premières pièces, * facons[s] est le nombre de façons de rendre s avec ces pièces. */ long nb_facons_rendre(int somme, int nb_pieces) { long *facons = calloc(somme + 1, sizeof(long)); facons[0] = 1; for (int p = 0; p < nb_pieces; p++) for (int s = PIECES[p]; s <= somme; s++) facons[s] += facons[s - PIECES[p]]; long resultat = facons[somme]; free(facons); return resultat; } /* 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. On passe la position du '\0' * final et on renvoie la nouvelle : plus besoin de recalculer la longueur. */ char *ajouter_ligne(char *fin, const char *ligne) { while (*ligne != '\0') *fin++ = *ligne++; *fin = '\0'; return fin; } /* ------------------------------------------------------------------ */ /* 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'; char *fin_rapport = rapport; /* position du '\0' final du rapport */ /* recherches par id, dans l'ordre d'origine */ long somme_requetes = 0; unsigned long etat = 12345; int *position = indexer_par_id(t, NB_TRANSACTIONS); for (int q = 0; q < NB_REQUETES; q++) { etat = etat * 1103515245UL + 12345UL; const Transaction *r = rechercher_par_id(t, position, (int)((etat >> 8) % NB_TRANSACTIONS)); somme_requetes += r->montant; /* tout id de 0 à n-1 existe : r n'est jamais NULL */ } free(position); 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); int categories[12] = {0}; for (int i = 0; i < NB_TRANSACTIONS; i++) categories[categorie(&t[i])]++; trier_par_montant(t, NB_TRANSACTIONS); long nb_paires = nb_paires_opposees(t, NB_TRANSACTIONS); /* a besoin du tri */ long nb_facons = nb_facons_rendre(MONTANT_MONNAIE, NB_PIECES); /* rapport */ snprintf(ligne, sizeof(ligne), "clients distincts;%d\n", nb_clients); fin_rapport = ajouter_ligne(fin_rapport, ligne); snprintf(ligne, sizeof(ligne), "somme des requetes;%ld\n", somme_requetes); fin_rapport = ajouter_ligne(fin_rapport, ligne); snprintf(ligne, sizeof(ligne), "meilleure periode;%d;%d;%ld\n", debut, fin, meilleure); fin_rapport = ajouter_ligne(fin_rapport, ligne); snprintf(ligne, sizeof(ligne), "paires opposees;%ld\n", nb_paires); fin_rapport = ajouter_ligne(fin_rapport, ligne); snprintf(ligne, sizeof(ligne), "facons de rendre %d;%ld\n", MONTANT_MONNAIE, nb_facons); fin_rapport = ajouter_ligne(fin_rapport, ligne); for (int c = 0; c < 12; c++) { snprintf(ligne, sizeof(ligne), "categorie;%d;%d\n", c, categories[c]); fin_rapport = ajouter_ligne(fin_rapport, ligne); } for (int c = 0; c < NB_CLIENTS; c++) { snprintf(ligne, sizeof(ligne), "client;%d;%ld\n", c, totaux_clients[c]); fin_rapport = ajouter_ligne(fin_rapport, ligne); } for (int j = 0; j < NB_JOURS; j++) { snprintf(ligne, sizeof(ligne), "jour;%d;%ld\n", j, totaux_jours[j]); fin_rapport = ajouter_ligne(fin_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); fin_rapport = ajouter_ligne(fin_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; }