Files
2026_TD3_DEV51_Qualite_Algo/banque.c
T
Ryad Mehiri 922703999f F5 : meilleure période en un seul parcours (O(J³) → O(J))
La triple boucle recalculait la somme de chaque période (i, j) :
J(J+1)(J+2)/6 = 167 millions d'additions pour 1 000 jours. Avec les
sommes préfixes, la meilleure période finissant au jour j commence
juste après le plus petit préfixe vu jusque-là : un seul parcours
suffit. En gardant le premier minimum et en ne remplaçant le meilleur
que s'il est strictement dépassé, on retrouve la même période que
l'original en cas d'égalité (vérifié sur 200 000 tableaux aléatoires).

Temps (-O2) : 0,25 s → 0,10 s. Empreinte inchangée (ff68d260a7a58441).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 10:55:53 +02:00

365 lines
12 KiB
C

/*
* 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 <stdio.h>
#include <stdlib.h>
#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;
}