Esercizio - Sistema tutor autostradale (progetto finale 2025-26)
In questa pagina 4
Testo (riassunto fedele). Sviluppare il software di un sistema tutor che controlla i veicoli in transito su un'autostrada a senso unico. L'autostrada ha due o più varchi, che registrano il passaggio dei veicoli, e svincoli dove i veicoli entrano o escono. Dai passaggi il sistema calcola la velocità media su ogni tratta tra due varchi consecutivi e segnala chi supera il limite di 130 km/h. Per provarlo serve un simulatore (eseguibile diverso) che genera il moto dei veicoli e i passaggi sotto i varchi.
Autostrada. Origine al km 0; file di testo Data/Highway.txt (nella radice del progetto), una riga per elemento: <distanza in km> <V|S> (varco o svincolo). Ogni varco e ogni svincolo ha un numero assegnato dal sistema in ordine crescente di distanza (le numerazioni sono indipendenti). Vincoli: almeno due varchi; almeno uno svincolo prima del primo varco e uno dopo l'ultimo; distanza minima tra svincolo e varco 1 km; elementi in qualsiasi ordine nel file. Descrizioni non conformi vanno rifiutate (il sistema può uscire con errore).
Simulatore. Per ogni veicolo, a caso: targa [due lettere][tre cifre][due lettere], svincolo di ingresso, svincolo di uscita, istante di ingresso, profilo di velocità. Il profilo è una successione di intervalli a velocità costante: l'intervallo i-esimo è percorso a per un tempo ; e sono interi casuali in km/h e minuti (costanti nel codice). Si generano intervalli a sufficienza per coprire la distanza tra ingresso e uscita; il cambio di velocità è istantaneo; all'uscita la velocità passa a 0; l'ultimo intervallo può non essere percorso tutto. Il tempo è un double di secondi dall'avvio. Tra l'ingresso di un veicolo e il successivo passano da 0,5 a 10,0 secondi (casuali). I veicoli sono 10000 (costante; il sistema può essere provato con più). I percorsi si salvano in Data/Runs.txt, righe <targa> <# svincolo ingresso> <# svincolo uscita> <istante di partenza> <v0 t0>, <v1 t1>, ..., <vN-1 tN-1>; i passaggi sotto i varchi in Data/Passages.txt, righe <# varco> <targa> <istante di passaggio> (input del tutor).
Tutor. Parte all'istante 0 e attende comandi dall'utente: set_time <istante> (avanza di un tempo: numero puro = secondi, numero seguito da m = minuti, es. set_time 20, set_time 20m; analizza i transiti avvenuti nel tempo intercorso; errore se non è un avanzamento), reset (azzera tutto), stats. A ogni set_time stampa i veicoli sopra il limite: targa, tratta (varco di inizio e fine), velocità media, istanti di passaggio. stats: per ogni varco il numero di veicoli transitati e la media di veicoli al minuto; la velocità media dei veicoli; il numero di veicoli sanzionati. Per l'elevato numero di veicoli serve una scelta opportuna delle strutture dati.
Vincoli di consegna. CMake con un solo CMakeLists.txt nella radice, compilazione con -O2, solo libreria standard; più file sorgente (ogni file di un solo autore, indicato in un commento); codice commentato; Data/ con Highway.txt, esempi (anche tagliati) di Runs.txt e Passages.txt; README.txt; niente eseguibili. Si valutano: chiarezza e correttezza, gestione di memoria e strutture dati, efficienza, strumenti appropriati; penalizzano compilazione fallita, segmentation fault, memory leak, CMakeLists sbagliato, uso errato (assente o eccessivo) dell'ereditarietà, uso errato della standard library per gestire elementi multipli, mancato controllo dei limiti degli array e degli argomenti da riga di comando, allocazione dinamica inopportuna, cattivo stile, assenza dei file di log richiesti.
Teoria: C++ - buona interfaccia e progetti su più fileUna classe C++ ben progettata decide esplicitamente le sette operazioni essenziali (costruttore con argomenti, di default, di copia, assegnamento di copia, costruttore e assegnamento di spostamento, distruttore): se serve il distruttore perché si possiede una risorsa, servono quasi sempre anche copia e spostamento; un costruttore a un argomento definisce una conversione implicita, che si blocca con explicit; per capire quando i costruttori e il distruttore vengono chiamati si traccia una struct che stampa ogni chiamata. Un progetto su più file separa interfaccia (header .h con definizioni di classe e dichiarazioni, protetto da include guard) e implementazione (.cpp, compilati uno per uno, mai inclusi); al compilatore si passano solo i .cpp, il main sta in un file a sé; gli errori di compilazione (sintassi, tipi, dichiarazioni mancanti) vengono dal compilatore, quelli di linking (definizione mancante, due main, nessun main) dal linker.C++ - buona interfaccia e progetti su più file →, CMake - build di progetti C++Un progetto C++ non è fatto solo di sorgenti: bisogna dire quali file formano ogni eseguibile e ogni libreria (target), quali librerie linkare, dove stanno gli header e con quale standard compilare. CMake è un meta-sistema di build indipendente da compilatore e piattaforma: si descrive il progetto in un file di testo CMakeLists.txt (cmake_minimum_required, project, add_library, add_executable, target_link_libraries, include directories) e CMake genera i file del sistema di build nativo (Makefile, progetto di IDE). Lato utente si fa una configurazione (mkdir build, cd build, cmake ..) e poi la compilazione (make o cmake --build .) in una cartella di build separata dai sorgenti; le librerie di soli template (header-only) si dichiarano INTERFACE perché non producono codice precompilato.CMake - build di progetti C++ →, STL - container associativi (map e set)I container associativi memorizzano gli elementi in base a una chiave: map è un contenitore ordinato di coppie (chiave, valore) ottimizzato per cercare dalla chiave (albero bilanciato, ricerca in tempo logaritmico), con operator[] che crea l'elemento se la chiave manca (il valore è inizializzato con il costruttore di default, quindi ++words[s] conta le occorrenze); gli elementi sono pair<const Chiave, Valore> con campi first e second. set è una map senza valori: contiene solo chiavi ordinate, senza [] né push_back, si usa con insert ed erase, e per i tipi definiti dall'utente richiede una funzione d'ordine (un function object). Le varianti: multimap e multiset ammettono chiavi ripetute; le versioni unordered_* usano tabelle hash e non mantengono l'ordine. Header , STL - algoritmi, predicati e lambdaLa STL ha circa 80 algoritmi generici (header
1. Dal testo al progetto
Interpretazioni da fissare
Il testo lascia alcune ambiguità: conviene deciderle e scriverle nel README.txt.
| punto | decisione |
|---|---|
set_time 20 |
l'argomento è un intervallo da aggiungere all'istante corrente (come dice il testo: "di quanto tempo avanzare"); set_time 0, negativo o non numerico dà errore |
| transiti analizzati | quelli con tempo in |
formato di Runs.txt |
<v_i t_i> sono coppie: targa ingresso uscita partenza v0 t0, v1 t1, ... |
| velocità media di una tratta | ; si calcola quando un veicolo passa sotto un varco dopo essere passato sotto un altro |
| limite | multa se la velocità media è strettamente maggiore di 130 km/h |
stats: veicoli al minuto |
transiti del varco diviso i minuti trascorsi dall'istante 0 all'istante corrente (0 se l'istante è 0) |
stats: velocità media dei veicoli |
media di tutte le velocità medie di tratta misurate finora |
stats: veicoli sanzionati |
targhe distinte sanzionate (un veicolo multato due volte conta uno) |
| veicolo che entra tra due varchi, o esce prima del successivo | non ha una tratta completa: nessuna misura |
| ingresso e uscita | l'uscita è sempre uno svincolo dopo l'ingresso (senso unico); l'ingresso non è l'ultimo svincolo |
| targhe | tutte diverse (con combinazioni e 10000 veicoli la probabilità di una coppia uguale è circa il 10%: il tutor, che identifica i veicoli dalla targa, li confonderebbe) |
Classi
| classe | responsabilità |
|---|---|
Highway |
leggere e validare Highway.txt; numerare varchi e svincoli; fornire i km |
Simulator |
generare i percorsi (Run), calcolare i passaggi (Passage), scrivere i due file |
Tutor |
analizzare i passaggi in ordine di tempo, misurare le velocità, sanzionare, tenere le statistiche, eseguire i comandi |
Marker, Segment, Run, Passage |
strutture dati semplici (nessun invariante), come Point nel corso |
main_simulator.cpp, main_tutor.cpp |
due eseguibili: argomenti, apertura dei file, scambio con l'utente |
Ereditarietà: nessuna. Le tre classi non sono varianti di un concetto comune e non c'è polimorfismo da sfruttare: una gerarchia sarebbe eccessiva, e la griglia del corso penalizza l'ereditarietà inutile quanto quella assente. (I comandi del tutor sono tre e si distinguono con un if: un'ipotetica classe Command con tre derivate sarebbe cerimonia senza vantaggi.) Highway è condivisa da simulatore e tutor: è l'unico punto in cui è definita la geometria.
Strutture dati e costi
| dato | struttura | perché |
|---|---|---|
| passaggi | vector<Passage> ordinato per tempo + indice next_ |
il tutor li consuma in ordine: ogni set_time riparte dall'indice, nessuna ricerca, ogni passaggio visto una volta ( in tutto, ordinamento iniziale ) |
| ultimo varco di un veicolo | unordered_map<string, Last> |
accesso per targa in tempo medio costante; la map (albero) costerebbe e non serve l'ordine |
| veicoli sanzionati | unordered_set<string> |
conta le targhe distinte senza duplicati |
| conteggi per varco | vector<long> indicizzato dal numero del varco |
accesso diretto, pochi varchi |
| varchi, svincoli | vector<Marker> ordinato per km |
numero piccolo: l'indice è id - 1 |
| profilo di velocità | vector<Segment> in ogni Run |
lunghezza variabile, nessuna new/delete esplicita: nessuna allocazione dinamica manuale nel progetto |
Nessun puntatore grezzo e nessun array C: la memoria è tutta gestita dai contenitori standard, quindi niente leak e niente accessi fuori dai limiti (l'indice id - 1 è sempre valido perché read_passages rifiuta numeri di varco fuori da 1..numero_varchi).
Come si calcola il passaggio sotto un varco
Un veicolo che entra allo svincolo al tempo e ha il profilo raggiunge il km (misurato dallo svincolo di ingresso) così: si percorrono gli intervalli uno dopo l'altro; l'intervallo copre km (con in minuti) e dura secondi; nel primo intervallo in cui la distanza residua è si aggiunge secondi. Controllo a mano sui dati veri della prova: il veicolo RO388TO entra allo svincolo 4 (km 60) al tempo 6,858 con un solo intervallo 156 14; il varco 4 è al km 75, quindi km e il passaggio avviene a s, come scritto in Passages.txt (riga 4 RO388TO 353.012). Per WG617ZA (svincolo 1, km 0, profilo 132 5, 92 11, ...) il varco 2 al km 25 cade nel secondo intervallo: il primo copre km in 300 s; restano 14 km a 92 km/h, cioè s; totale s, come in Passages.txt.
Per la velocità media: EX876BJ passa sotto il varco 1 (km 5) a 230,834 s e sotto il varco 2 (km 25) a 617,931 s: km/h, sopra il limite, e infatti il tutor la sanziona (vedi l'uscita).
2. Il codice
Struttura:
tutor/
├── CMakeLists.txt
├── README.txt
├── Data/ Highway.txt (e, generati, Runs.txt e Passages.txt)
├── include/ highway.h simulator.h tutor.h
└── src/ highway.cpp simulator.cpp tutor.cpp
main_simulator.cpp main_tutor.cpp test_integrato.cppCMakeLists.txt (uno solo, nella radice; -O2; due librerie e due eseguibili, più la prova integrata):
cmake_minimum_required(VERSION 3.10)
project(SistemaTutor LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_compile_options(-O2 -Wall -Wextra) # ottimizzazioni richieste dal progetto
include_directories(include)
add_library(highway src/highway.cpp)
add_library(simulatorlib src/simulator.cpp)
target_link_libraries(simulatorlib PUBLIC highway)
add_library(tutorlib src/tutor.cpp)
target_link_libraries(tutorlib PUBLIC simulatorlib) # Tutor usa Passage, definita in simulator.h
add_executable(simulator src/main_simulator.cpp)
target_link_libraries(simulator PRIVATE simulatorlib)
add_executable(tutor src/main_tutor.cpp)
target_link_libraries(tutor PRIVATE tutorlib)
add_executable(test_integrato src/test_integrato.cpp)
target_link_libraries(test_integrato PRIVATE tutorlib)(Il file non è stato eseguito con cmake, che non è installato nell'ambiente di prova; la sequenza equivalente di compilazione e linking è stata provata a mano, vedi la sezione 3.)
Autostrada
#ifndef HIGHWAY_H
#define HIGHWAY_H
#include <iosfwd>
#include <string>
#include <vector>
// Un varco o uno svincolo: numero progressivo (da 1, in ordine di distanza crescente) e posizione in km.
struct Marker {
int id;
double km;
};
// Descrizione dell'autostrada letta da Highway.txt, una riga per elemento: "<km> <V|S>".
// Invarianti dopo un parse riuscito:
// - almeno due varchi;
// - almeno uno svincolo prima del primo varco e uno dopo l'ultimo;
// - tra uno svincolo e un varco ci sono almeno kMinDistance km;
// - due varchi non sono nello stesso punto;
// - varchi e svincoli sono ordinati per km e numerati da 1 (le due numerazioni sono indipendenti).
class Highway {
public:
static constexpr double kMinDistance = 1.0;
bool parse(std::istream& in, std::string& error); // false + messaggio se la descrizione non e' valida
bool valid() const { return !gates_.empty(); }
const std::vector<Marker>& gates() const { return gates_; }
const std::vector<Marker>& junctions() const { return junctions_; }
double gate_km(int id) const { return gates_[id - 1].km; }
double junction_km(int id) const { return junctions_[id - 1].km; }
private:
std::vector<Marker> gates_;
std::vector<Marker> junctions_;
};
#endif // HIGHWAY_H#include "highway.h"
#include <algorithm>
#include <cmath>
#include <istream>
#include <sstream>
namespace {
bool by_km(const Marker& a, const Marker& b) { return a.km < b.km; }
}
bool Highway::parse(std::istream& in, std::string& error)
{
std::vector<Marker> gates, junctions; // si lavora su copie locali
std::string line;
int line_no = 0;
while (std::getline(in, line)) {
++line_no;
if (line.find_first_not_of(" \t\r") == std::string::npos) continue; // riga vuota
std::istringstream fields{line};
double km;
char kind;
std::string extra;
if (!(fields >> km >> kind) || (fields >> extra) || (kind != 'V' && kind != 'S') || km < 0) {
error = "riga " + std::to_string(line_no) + " non valida (atteso: <km >= 0> <V|S>)";
return false;
}
(kind == 'V' ? gates : junctions).push_back(Marker{0, km});
}
if (gates.size() < 2) { error = "servono almeno due varchi"; return false; }
std::sort(gates.begin(), gates.end(), by_km);
std::sort(junctions.begin(), junctions.end(), by_km);
for (std::size_t i = 0; i < gates.size(); ++i) gates[i].id = static_cast<int>(i) + 1;
for (std::size_t i = 0; i < junctions.size(); ++i) junctions[i].id = static_cast<int>(i) + 1;
for (std::size_t i = 1; i < gates.size(); ++i)
if (gates[i].km == gates[i - 1].km) { error = "due varchi alla stessa distanza"; return false; }
if (junctions.empty() || junctions.front().km >= gates.front().km) {
error = "serve uno svincolo prima del primo varco";
return false;
}
if (junctions.back().km <= gates.back().km) {
error = "serve uno svincolo dopo l'ultimo varco";
return false;
}
for (const Marker& j : junctions)
for (const Marker& g : gates)
if (std::fabs(j.km - g.km) < kMinDistance) {
error = "uno svincolo e un varco distano meno di 1 km";
return false;
}
gates_ = gates; // l'oggetto cambia solo a controlli superati
junctions_ = junctions;
return true;
}parse lavora su copie locali (gates, junctions) e assegna ai membri solo a controlli superati: se la descrizione è sbagliata l'oggetto non viene toccato e il programma può uscire con un messaggio. L'ordine dei controlli: formato di ogni riga (due campi, V o S, km non negativo, nessun resto), almeno due varchi, ordinamento e numerazione, varchi distinti, svincolo prima del primo e dopo l'ultimo varco, distanza di almeno 1 km tra ogni svincolo e ogni varco (il confronto è < kMinDistance: esattamente 1 km è ammesso). Il controllo di formato è necessario perché il file è scritto a mano.
Simulatore
#ifndef SIMULATOR_H
#define SIMULATOR_H
#include <iosfwd>
#include <random>
#include <string>
#include <vector>
#include "highway.h"
// Un intervallo percorso a velocita' costante.
struct Segment {
int speed_kmh;
int minutes;
};
// Il percorso di un veicolo: da uno svincolo all'altro, con il suo profilo di velocita'.
struct Run {
std::string plate;
int entry; // numero dello svincolo di ingresso
int exit; // numero dello svincolo di uscita (piu' avanti)
double start; // istante di ingresso, in secondi dall'avvio del simulatore
std::vector<Segment> profile;
};
// Il passaggio di un veicolo sotto un varco: e' l'input del sistema tutor.
struct Passage {
int gate;
std::string plate;
double time; // secondi dall'avvio
};
// Parametri della simulazione (i valori richiesti dal progetto).
struct SimulatorConfig {
int min_speed = 80, max_speed = 190; // km/h
int min_minutes = 5, max_minutes = 15; // durata di ogni intervallo
double min_gap = 0.5, max_gap = 10.0; // secondi tra l'ingresso di un veicolo e il successivo
};
class Simulator {
public:
Simulator(const Highway& highway, unsigned seed, SimulatorConfig config = SimulatorConfig{});
std::vector<Run> generate_runs(int count); // Runs.txt
std::vector<Passage> compute_passages(const std::vector<Run>& runs) const; // Passages.txt, ordinati per tempo
static void write_runs(std::ostream& os, const std::vector<Run>& runs);
static void write_passages(std::ostream& os, const std::vector<Passage>& passages);
private:
int rand_int(int lo, int hi); // intero uniforme in [lo, hi]
double rand_real(double lo, double hi); // reale uniforme in [lo, hi)
std::string random_plate(); // "AB123CD"
double seconds_to_reach(const Run& run, double km_from_entry) const;
const Highway& highway_;
std::mt19937 rng_;
SimulatorConfig config_;
};
#endif // SIMULATOR_H#include "simulator.h"
#include <algorithm>
#include <iomanip>
#include <ostream>
#include <unordered_set>
Simulator::Simulator(const Highway& highway, unsigned seed, SimulatorConfig config)
: highway_{highway}, rng_{seed}, config_{config} {}
// mt19937 e' definito dallo standard bit per bit: a parita' di seme dà la stessa sequenza su ogni
// compilatore (le distribuzioni di <random>, invece, no). Il resto usato qui ha una distorsione
// trascurabile (intervalli di poche decine di valori su 2^32).
int Simulator::rand_int(int lo, int hi)
{
return lo + static_cast<int>(rng_() % static_cast<unsigned long>(hi - lo + 1));
}
double Simulator::rand_real(double lo, double hi)
{
return lo + (hi - lo) * (static_cast<double>(rng_()) / 4294967296.0);
}
std::string Simulator::random_plate()
{
std::string p;
for (int i = 0; i < 2; ++i) p += static_cast<char>('A' + rand_int(0, 25));
for (int i = 0; i < 3; ++i) p += static_cast<char>('0' + rand_int(0, 9));
for (int i = 0; i < 2; ++i) p += static_cast<char>('A' + rand_int(0, 25));
return p;
}
std::vector<Run> Simulator::generate_runs(int count)
{
std::vector<Run> runs;
runs.reserve(static_cast<std::size_t>(count));
std::unordered_set<std::string> used; // le targhe devono essere tutte diverse
const int junctions = static_cast<int>(highway_.junctions().size());
double t = 0.0;
for (int i = 0; i < count; ++i) {
t += rand_real(config_.min_gap, config_.max_gap); // ingresso del veicolo successivo
Run run;
do { run.plate = random_plate(); } while (!used.insert(run.plate).second);
run.entry = rand_int(1, junctions - 1); // non l'ultimo: da li' non si puo' uscire
run.exit = rand_int(run.entry + 1, junctions); // uscita piu' avanti dell'ingresso
run.start = t;
double distance = highway_.junction_km(run.exit) - highway_.junction_km(run.entry);
double covered = 0.0;
while (covered < distance) { // intervalli fino a coprire la distanza
Segment s{rand_int(config_.min_speed, config_.max_speed), rand_int(config_.min_minutes, config_.max_minutes)};
run.profile.push_back(s);
covered += s.speed_kmh * s.minutes / 60.0;
}
runs.push_back(run);
}
return runs;
}
// Secondi (dall'ingresso) necessari per arrivare a km_from_entry km dallo svincolo di ingresso.
double Simulator::seconds_to_reach(const Run& run, double km_from_entry) const
{
double elapsed = 0.0, remaining = km_from_entry;
for (const Segment& s : run.profile) {
double length = s.speed_kmh * s.minutes / 60.0; // km percorsi in questo intervallo
if (remaining <= length) return elapsed + remaining / s.speed_kmh * 3600.0;
remaining -= length;
elapsed += s.minutes * 60.0;
}
return elapsed; // non succede: il profilo copre la distanza
}
std::vector<Passage> Simulator::compute_passages(const std::vector<Run>& runs) const
{
std::vector<Passage> passages;
for (const Run& run : runs) {
double from = highway_.junction_km(run.entry);
double to = highway_.junction_km(run.exit);
for (const Marker& gate : highway_.gates())
if (gate.km > from && gate.km < to) // i varchi incontrati nel tragitto
passages.push_back(Passage{gate.id, run.plate, run.start + seconds_to_reach(run, gate.km - from)});
}
std::sort(passages.begin(), passages.end(), [](const Passage& a, const Passage& b) {
if (a.time != b.time) return a.time < b.time;
return a.gate < b.gate;
});
return passages;
}
void Simulator::write_runs(std::ostream& os, const std::vector<Run>& runs)
{
os << std::fixed << std::setprecision(3);
for (const Run& r : runs) {
os << r.plate << ' ' << r.entry << ' ' << r.exit << ' ' << r.start;
for (std::size_t i = 0; i < r.profile.size(); ++i)
os << (i == 0 ? " " : ", ") << r.profile[i].speed_kmh << ' ' << r.profile[i].minutes;
os << '\n';
}
}
void Simulator::write_passages(std::ostream& os, const std::vector<Passage>& passages)
{
os << std::fixed << std::setprecision(3);
for (const Passage& p : passages) os << p.gate << ' ' << p.plate << ' ' << p.time << '\n';
}Scelte:
- Casualità riproducibile.
std::mt19937con seme esplicito (il seme predefinito è 2026): la sequenza dimt19937è fissata dallo standard, quindi a parità di semeRuns.txtè identico su ogni compilatore. Gli interi si ottengono conrng_() % intervallo(distorsione dell'ordine di , trascurabile) e i reali conrng_() / 2^32; le distribuzioni di<random>si evitano perché le implementazioni differiscono tra librerie. - Targhe uniche con un
unordered_sete un ciclodo ... while (!used.insert(...).second). - Ingresso e uscita: l'ingresso è scelto tra gli svincoli ( = numero di svincoli), l'uscita tra : l'uscita è sempre dopo, e la scelta non può restare senza candidati.
- Profilo: si aggiungono intervalli finché la distanza coperta raggiunge la distanza tra gli svincoli; l'ultimo intervallo resta intero nel file anche se il veicolo esce prima della sua fine (
seconds_to_reachsi ferma alla distanza richiesta). - Passaggi: per ogni veicolo i varchi con
from < km < to; poi un solosortglobale per tempo (a parità, per numero di varco). - Il file dei passaggi si scrive con tre decimali (
fixed << setprecision(3)): la precisione di default (6 cifre significative) taglierebbe i decimali dei tempi grandi.
Tutor
#ifndef TUTOR_H
#define TUTOR_H
#include <iosfwd>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "highway.h"
#include "simulator.h"
// Legge Passages.txt ("<# varco> <targa> <istante>" per riga); false + messaggio se una riga non e' valida.
bool read_passages(std::istream& in, int gate_count, std::vector<Passage>& out, std::string& error);
// Il sistema tutor: scorre i passaggi in ordine di tempo, calcola la velocita' media di ogni veicolo
// su ogni tratta tra due varchi consecutivi e segnala chi supera il limite.
class Tutor {
public:
static constexpr double kSpeedLimit = 130.0; // km/h
// 'passages' puo' essere in qualsiasi ordine: il costruttore lo ordina per tempo.
Tutor(const Highway& highway, std::vector<Passage> passages);
void set_time(double delta_seconds, std::ostream& out); // avanza e analizza i transiti nell'intervallo
void reset(); // torna all'istante 0, statistiche azzerate
void stats(std::ostream& out) const;
// Esegue una riga di comando ("set_time 20m", "reset", "stats"). false se il comando e' "quit".
bool execute(const std::string& line, std::ostream& out);
double now() const { return now_; }
private:
struct Last { // ultimo varco visto per un veicolo
int gate;
double time;
};
void process(const Passage& p, std::ostream& out);
const Highway& highway_;
std::vector<Passage> passages_; // ordinati per tempo
std::size_t next_; // primo passaggio non ancora analizzato
double now_; // istante corrente (secondi)
std::unordered_map<std::string, Last> last_; // targa -> ultimo varco: accesso in O(1)
std::vector<long> gate_count_; // veicoli transitati, per varco (indice = numero - 1)
double speed_sum_; // somma delle velocita' medie misurate
long speed_n_; // numero di misure
std::unordered_set<std::string> sanctioned_; // targhe sanzionate (senza duplicati)
};
#endif // TUTOR_H#include "tutor.h"
#include <algorithm>
#include <iomanip>
#include <istream>
#include <ostream>
#include <sstream>
#include <utility>
bool read_passages(std::istream& in, int gate_count, std::vector<Passage>& out, std::string& error)
{
std::vector<Passage> result;
std::string line;
int line_no = 0;
while (std::getline(in, line)) {
++line_no;
if (line.find_first_not_of(" \t\r") == std::string::npos) continue;
std::istringstream fields{line};
Passage p;
if (!(fields >> p.gate >> p.plate >> p.time) || p.gate < 1 || p.gate > gate_count || p.time < 0) {
error = "riga " + std::to_string(line_no) + " di Passages non valida";
return false;
}
result.push_back(p);
}
out = result;
return true;
}
Tutor::Tutor(const Highway& highway, std::vector<Passage> passages)
: highway_{highway}, passages_{std::move(passages)}, next_{0}, now_{0.0},
last_{}, gate_count_(highway.gates().size(), 0), speed_sum_{0.0}, speed_n_{0}, sanctioned_{}
{
std::stable_sort(passages_.begin(), passages_.end(),
[](const Passage& a, const Passage& b) { return a.time < b.time; });
}
void Tutor::reset()
{
next_ = 0;
now_ = 0.0;
last_.clear();
std::fill(gate_count_.begin(), gate_count_.end(), 0L);
speed_sum_ = 0.0;
speed_n_ = 0;
sanctioned_.clear();
}
// Un passaggio sotto il varco p.gate: se il veicolo e' gia' passato sotto un varco precedente,
// la velocita' media sulla tratta e' (km tra i due varchi) / (tempo impiegato).
void Tutor::process(const Passage& p, std::ostream& out)
{
++gate_count_[static_cast<std::size_t>(p.gate - 1)];
auto it = last_.find(p.plate);
if (it != last_.end() && p.time > it->second.time) {
double km = highway_.gate_km(p.gate) - highway_.gate_km(it->second.gate);
double speed = km / ((p.time - it->second.time) / 3600.0);
speed_sum_ += speed;
++speed_n_;
if (speed > kSpeedLimit) {
sanctioned_.insert(p.plate);
out << "SANZIONE " << p.plate << ": tratta varco " << it->second.gate << " -> varco " << p.gate
<< ", " << std::fixed << std::setprecision(1) << speed << " km/h, passaggi a "
<< std::setprecision(3) << it->second.time << " s e " << p.time << " s\n";
}
}
last_[p.plate] = Last{p.gate, p.time}; // il veicolo ora e' sotto questo varco
}
void Tutor::set_time(double delta_seconds, std::ostream& out)
{
now_ += delta_seconds;
long analyzed = 0;
while (next_ < passages_.size() && passages_[next_].time <= now_) { // i transiti in (vecchio, nuovo]
process(passages_[next_], out);
++next_;
++analyzed;
}
out << "istante " << std::fixed << std::setprecision(1) << now_ << " s: " << analyzed << " transiti analizzati\n";
}
void Tutor::stats(std::ostream& out) const
{
out << "istante " << std::fixed << std::setprecision(1) << now_ << " s\n";
for (std::size_t g = 0; g < gate_count_.size(); ++g) {
double per_minute = now_ > 0 ? gate_count_[g] / (now_ / 60.0) : 0.0;
out << "varco " << g + 1 << ": " << gate_count_[g] << " veicoli, " << std::setprecision(2) << per_minute
<< " al minuto\n";
}
out << "velocita' media: " << std::setprecision(1) << (speed_n_ ? speed_sum_ / static_cast<double>(speed_n_) : 0.0)
<< " km/h (" << speed_n_ << " misure)\n";
out << "veicoli sanzionati: " << sanctioned_.size() << '\n';
}
bool Tutor::execute(const std::string& line, std::ostream& out)
{
std::istringstream in{line};
std::string cmd;
if (!(in >> cmd)) return true; // riga vuota
if (cmd == "quit") return false;
if (cmd == "reset") { reset(); out << "sistema azzerato\n"; return true; }
if (cmd == "stats") { stats(out); return true; }
if (cmd == "set_time") {
std::string arg;
in >> arg;
double factor = 1.0; // numero puro = secondi, con 'm' finale = minuti
if (!arg.empty() && arg.back() == 'm') { factor = 60.0; arg.pop_back(); }
std::istringstream number{arg};
double value;
if (arg.empty() || !(number >> value) || !number.eof() || value <= 0) {
out << "errore: set_time richiede un intervallo positivo (es. 20 oppure 20m)\n";
return true;
}
set_time(value * factor, out);
return true;
}
out << "errore: comando sconosciuto '" << cmd << "' (set_time, reset, stats, quit)\n";
return true;
}Punti da giustificare:
processaggiorna sempre il contatore del varco e l'ultimo passaggio del veicolo (last_[plate] = ...); la misura di velocità si fa solo se la targa era già inlast_. Così un veicolo che entra dopo il primo varco non produce misure spurie, e uno che passa due varchi consecutivi produce esattamente una misura per tratta.set_timeconsuma i passaggi contime <= now_a partire danext_: ogni passaggio è analizzato una sola volta anche seset_timeviene chiamato mille volte;resetriportanext_a 0 senza ricaricare né riordinare.executesepara l'interfaccia testuale dalla logica (set_time(double),stats,resetsi possono chiamare direttamente, come fa la prova integrata). Il suffissomsi gestisce togliendo l'ultimo carattere; il numero si legge conistringstream(non constod, che lancia eccezioni) e si controllanumber.eof()per rifiutare20x.- I messaggi di errore dei comandi non terminano il programma: il tutor resta in ascolto.
I due eseguibili
// Simulatore: legge Data/Highway.txt e scrive Data/Runs.txt e Data/Passages.txt.
// uso: simulator [numero_veicoli [seme]]
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include "highway.h"
#include "simulator.h"
namespace {
constexpr int kDefaultVehicles = 10000; // numero di veicoli simulati (il sistema deve reggerne di piu')
constexpr unsigned kDefaultSeed = 2026;
const char* const kHighwayFile = "Data/Highway.txt";
const char* const kRunsFile = "Data/Runs.txt";
const char* const kPassagesFile = "Data/Passages.txt";
bool read_number(const char* text, long& value) // argomento da riga di comando, controllato
{
std::istringstream in{text};
return (in >> value) && in.eof() && value > 0;
}
}
int main(int argc, char** argv)
{
if (argc > 3) {
std::cerr << "uso: " << argv[0] << " [numero_veicoli [seme]]\n";
return 1;
}
long count = kDefaultVehicles, seed = kDefaultSeed;
if (argc >= 2 && !read_number(argv[1], count)) { std::cerr << "numero_veicoli non valido\n"; return 1; }
if (argc == 3 && !read_number(argv[2], seed)) { std::cerr << "seme non valido\n"; return 1; }
std::ifstream highway_in{kHighwayFile};
if (!highway_in) { std::cerr << "impossibile aprire " << kHighwayFile << '\n'; return 1; }
Highway highway;
std::string error;
if (!highway.parse(highway_in, error)) {
std::cerr << "autostrada non valida: " << error << '\n';
return 1; // descrizione non conforme: il sistema esce con errore
}
Simulator simulator{highway, static_cast<unsigned>(seed)};
std::vector<Run> runs = simulator.generate_runs(static_cast<int>(count));
std::vector<Passage> passages = simulator.compute_passages(runs);
std::ofstream runs_out{kRunsFile}, passages_out{kPassagesFile};
if (!runs_out || !passages_out) { std::cerr << "impossibile scrivere in Data/\n"; return 1; }
Simulator::write_runs(runs_out, runs);
Simulator::write_passages(passages_out, passages);
std::cout << runs.size() << " percorsi in " << kRunsFile << ", " << passages.size() << " passaggi in "
<< kPassagesFile << '\n';
return 0;
}// Sistema tutor: legge Highway.txt e Passages.txt, poi esegue i comandi letti dallo standard input.
// uso: tutor [Highway.txt Passages.txt] (predefiniti: Data/Highway.txt Data/Passages.txt)
// comandi: set_time <secondi>|<minuti>m reset stats quit
#include <fstream>
#include <iostream>
#include <string>
#include "highway.h"
#include "tutor.h"
int main(int argc, char** argv)
{
if (argc != 1 && argc != 3) {
std::cerr << "uso: " << argv[0] << " [Highway.txt Passages.txt]\n";
return 1;
}
std::string highway_file = argc == 3 ? argv[1] : "Data/Highway.txt";
std::string passages_file = argc == 3 ? argv[2] : "Data/Passages.txt";
std::ifstream highway_in{highway_file};
if (!highway_in) { std::cerr << "impossibile aprire " << highway_file << '\n'; return 1; }
Highway highway;
std::string error;
if (!highway.parse(highway_in, error)) {
std::cerr << "autostrada non valida: " << error << '\n';
return 1;
}
std::ifstream passages_in{passages_file};
if (!passages_in) { std::cerr << "impossibile aprire " << passages_file << '\n'; return 1; }
std::vector<Passage> passages;
if (!read_passages(passages_in, static_cast<int>(highway.gates().size()), passages, error)) {
std::cerr << error << '\n';
return 1;
}
Tutor tutor{highway, passages};
std::cout << passages.size() << " passaggi caricati, " << highway.gates().size() << " varchi\n";
std::string line;
while (std::getline(std::cin, line))
if (!tutor.execute(line, std::cout)) break; // "quit"
return 0;
}Il controllo degli argomenti (voce della griglia): simulator accetta zero, uno o due argomenti numerici positivi (letti con istringstream, senza eccezioni) e stampa l'uso altrimenti; tutor accetta zero o due argomenti (i due file). In entrambi, un file che non si apre, un'autostrada non valida o una riga di Passages.txt sbagliata fanno uscire con codice 1 e un messaggio su cerr.
Data/Highway.txt di prova (volutamente in ordine sparso: varchi a 5, 25, 50, 75, 100 km; svincoli a 0, 12, 35, 60, 85, 110 km):
50 V
0 S
35 S
100 V
12 S
5 V
75 V
85 S
60 S
25 V
110 SProva integrata senza file
Per provare simulatore e tutor insieme senza file (e con uscita ripetibile) c'è un terzo programma che usa solo stringstream:
// Prova integrata: simulatore e tutor insieme, senza file (tutto passa per stringstream).
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include "highway.h"
#include "simulator.h"
#include "tutor.h"
namespace {
// L'autostrada di prova: elementi in ordine sparso. Varchi a 5, 25, 50, 75, 100 km; svincoli a 0, 12, 35, 60, 85, 110.
const char* const kHighway =
"50 V\n0 S\n35 S\n100 V\n12 S\n5 V\n75 V\n85 S\n60 S\n25 V\n110 S\n";
// stampa le prime max_lines righe di un testo e quante ce n'erano in tutto
void show(const std::string& text, int max_lines)
{
std::istringstream in{text};
std::string line;
int n = 0;
while (std::getline(in, line)) {
if (n < max_lines) std::cout << " " << line << '\n';
++n;
}
if (n > max_lines) std::cout << " ... (" << n << " righe in tutto)\n";
}
void command(Tutor& tutor, const std::string& line, int max_lines)
{
std::ostringstream out;
tutor.execute(line, out);
std::cout << "> " << line << '\n';
show(out.str(), max_lines);
}
}
int main()
{
std::cout << "== autostrade non valide ==\n";
const char* bad[] = {"5 V\n0 S\n20 S\n", "0 S\n5 V\n20 V\n", "0 S\n10 V\n10 V\n30 S\n",
"0 S\n10 V\n12 X\n", "0 S\n10 V\n30 V\n10.5 S\n50 S\n"};
for (const char* text : bad) {
std::istringstream in{text};
Highway h;
std::string error;
std::cout << (h.parse(in, error) ? "accettata?!" : error) << '\n';
}
std::cout << "== autostrada valida ==\n";
Highway highway;
std::string error;
std::istringstream highway_in{kHighway};
if (!highway.parse(highway_in, error)) { std::cout << error << '\n'; return 1; }
std::cout << "varchi (km):";
for (const Marker& g : highway.gates()) std::cout << ' ' << g.id << ':' << g.km;
std::cout << "\nsvincoli (km):";
for (const Marker& j : highway.junctions()) std::cout << ' ' << j.id << ':' << j.km;
std::cout << '\n';
std::cout << "== simulazione di 10000 veicoli (seme 2026) ==\n";
Simulator simulator{highway, 2026};
std::vector<Run> runs = simulator.generate_runs(10000);
std::vector<Passage> passages = simulator.compute_passages(runs);
std::ostringstream runs_txt, passages_txt;
Simulator::write_runs(runs_txt, runs);
Simulator::write_passages(passages_txt, passages);
std::cout << "Runs.txt (prime righe):\n";
show(runs_txt.str(), 3);
std::cout << "Passages.txt (prime righe):\n";
show(passages_txt.str(), 4);
std::vector<Passage> reread; // dal testo di Passages.txt, come fa il tutor
std::istringstream passages_in{passages_txt.str()};
if (!read_passages(passages_in, static_cast<int>(highway.gates().size()), reread, error)) {
std::cout << error << '\n';
return 1;
}
std::cout << "passaggi riletti: " << reread.size() << " (scritti: " << passages.size() << ")\n";
std::cout << "== sessione del tutor ==\n";
Tutor tutor{highway, reread};
command(tutor, "set_time 20", 4);
command(tutor, "set_time 10m", 4);
command(tutor, "stats", 12);
command(tutor, "set_time -5", 4);
command(tutor, "set_time abc", 4);
command(tutor, "frob", 4);
command(tutor, "reset", 4);
command(tutor, "stats", 12);
command(tutor, "set_time 2000m", 3); // tutta la simulazione
command(tutor, "stats", 12);
return 0;
}3. Prove ed esiti
Compilazione. Tutti i sorgenti compilano per Linux con -Wall -Wextra senza avvisi. La sequenza di CMake (librerie highway, simulatorlib, tutorlib e i tre eseguibili) è stata riprodotta a mano con il compilatore per WebAssembly (-O2).
Esecuzione. In WebAssembly manca <fstream>: i due eseguibili veri sono stati provati con un sostituto minimo di <fstream> (legge e scrive file con fopen), mentre test_integrato non ne ha bisogno. Uscita di test_integrato (le righe lunghe sono troncate dal programma stesso):
== autostrade non valide ==
servono almeno due varchi
serve uno svincolo dopo l'ultimo varco
due varchi alla stessa distanza
riga 3 non valida (atteso: <km >= 0> <V|S>)
uno svincolo e un varco distano meno di 1 km
== autostrada valida ==
varchi (km): 1:5 2:25 3:50 4:75 5:100
svincoli (km): 1:0 2:12 3:35 4:60 5:85 6:110
== simulazione di 10000 veicoli (seme 2026) ==
Runs.txt (prime righe):
WG617ZA 1 6 2.584 132 5, 92 11, 113 12, 143 11, 98 13, 110 7
RO388TO 4 5 6.858 156 14
FK971SH 1 2 10.813 163 14
... (10000 righe in tutto)
Passages.txt (prime righe):
1 FK971SH 121.243
1 WG617ZA 138.947
1 UE018FT 230.436
1 EX876BJ 230.834
... (20107 righe in tutto)
passaggi riletti: 20107 (scritti: 20107)La sessione del tutor di test_integrato:
> set_time 20
istante 20.0 s: 0 transiti analizzati
> set_time 10m
SANZIONE EX876BJ: tratta varco 1 -> varco 2, 186.0 km/h, passaggi a 230.834 s e 617.931 s
istante 620.0 s: 60 transiti analizzati
> stats
istante 620.0 s
varco 1: 23 veicoli, 2.23 al minuto
varco 2: 11 veicoli, 1.06 al minuto
varco 3: 8 veicoli, 0.77 al minuto
varco 4: 8 veicoli, 0.77 al minuto
varco 5: 10 veicoli, 0.97 al minuto
velocita' media: 186.0 km/h (1 misure)
veicoli sanzionati: 1
> set_time -5
errore: set_time richiede un intervallo positivo (es. 20 oppure 20m)
> set_time abc
errore: set_time richiede un intervallo positivo (es. 20 oppure 20m)
> frob
errore: comando sconosciuto 'frob' (set_time, reset, stats, quit)
> reset
sistema azzerato
> stats
istante 0.0 s
varco 1: 0 veicoli, 0.00 al minuto
...
veicoli sanzionati: 0
> set_time 2000m
SANZIONE EX876BJ: tratta varco 1 -> varco 2, 186.0 km/h, passaggi a 230.834 s e 617.931 s
SANZIONE QN319KW: tratta varco 1 -> varco 2, 176.0 km/h, passaggi a 304.371 s e 713.462 s
SANZIONE EK212UN: tratta varco 1 -> varco 2, 143.0 km/h, passaggi a 288.194 s e 791.690 s
... (6290 righe in tutto)
> stats
istante 120000.0 s
varco 1: 2042 veicoli, 1.02 al minuto
varco 2: 3602 veicoli, 1.80 al minuto
varco 3: 4689 veicoli, 2.34 al minuto
varco 4: 5137 veicoli, 2.57 al minuto
varco 5: 4637 veicoli, 2.32 al minuto
velocita' media: 139.7 km/h (10107 misure)
veicoli sanzionati: 4151Controlli di coerenza sull'uscita: dopo aver analizzato tutto, la somma dei conteggi dei varchi è , esattamente il numero di righe di Passages.txt; le misure di velocità sono : in questa autostrada tra due svincoli consecutivi c'è sempre un varco, quindi ogni veicolo passa sotto almeno un varco e ha una prima rilevazione senza tratta precedente (nessuna misura); i reset riportano stats a zero. Con una sessione sugli eseguibili veri (simulator e poi tutor con i file scritti in Data/):
$ simulator 10000 2026
10000 percorsi in Data/Runs.txt, 20107 passaggi in Data/Passages.txt
$ printf 'set_time 20m\nstats\nreset\nquit\n' | tutor
20107 passaggi caricati, 5 varchi
SANZIONE EX876BJ: tratta varco 1 -> varco 2, 186.0 km/h, passaggi a 230.834 s e 617.931 s
SANZIONE QN319KW: tratta varco 1 -> varco 2, 176.0 km/h, passaggi a 304.371 s e 713.462 s
...
istante 1200.0 s: 190 transiti analizzatiCon la Highway.txt non valida (0 S, 10 V, 30 S) tutor stampa autostrada non valida: servono almeno due varchi ed esce con codice 1; con un file dei passaggi inesistente stampa impossibile aprire ... ed esce con codice 1; simulator abc stampa numero_veicoli non valido.
4. Lista di controllo della consegna
- Struttura:
CMakeLists.txtunico nella radice,-O2,Data/Highway.txt, estratti diRuns.txtePassages.txt(tagliati se grandi),README.txtcon le decisioni della tabella iniziale e i problemi non risolti, nessun eseguibile; verifica finale scaricando la consegna in un'altra cartella e rifacendocmake ..,make. - Autori: ogni file ha in testa un commento con l'autore, e un solo autore.
- Memoria e strutture dati: nessuna
new/delete, nessun leak; contenitori adatti all'accesso (hash per targa, vettore ordinato per i passaggi). - Limiti: gli indici sono controllati nella lettura dei file e dagli argomenti; ogni argomento da riga di comando è verificato.
- Stile: funzioni brevi (la più lunga è
Tutor::execute), costanti con nome (kSpeedLimit,kDefaultVehicles,kMinDistance), nessun magic number sparso, include guard, header e implementazione separati,mainin file a sé. - Errori tipici del corso: ereditarietà inutile;
vectorricercato linearmente per targa a ogni passaggio (inefficiente con 10000 veicoli); allocazione dinamica manuale; mancato controllo degli argomenti;using namespace std;negli header; tutta la logica nelmain.
Versione ripasso
- Testo: tutor che calcola la velocità media di ogni veicolo tra varchi consecutivi e sanziona oltre 130 km/h; simulatore (eseguibile separato) che genera
Runs.txt(targa ingresso uscita partenza v0 t0, v1 t1, ...) ePassages.txt(varco targa istante).Data/Highway.txt:<km> <V|S>, numerazione per distanza, almeno 2 varchi, uno svincolo prima del primo e dopo l'ultimo, almeno 1 km tra svincolo e varco, ordine libero, file non conforme rifiutato. Velocità casuali 80-190 km/h, intervalli 5-15 min, ingressi ogni 0,5-10,0 s, 10000 veicoli. Comandiset_time <s>|<m>m,reset,stats. Un soloCMakeLists.txt,-O2, solo libreria standard. Teoria: CMake - build di progetti C++Un progetto C++ non è fatto solo di sorgenti: bisogna dire quali file formano ogni eseguibile e ogni libreria (target), quali librerie linkare, dove stanno gli header e con quale standard compilare. CMake è un meta-sistema di build indipendente da compilatore e piattaforma: si descrive il progetto in un file di testo CMakeLists.txt (cmake_minimum_required, project, add_library, add_executable, target_link_libraries, include directories) e CMake genera i file del sistema di build nativo (Makefile, progetto di IDE). Lato utente si fa una configurazione (mkdir build, cd build, cmake ..) e poi la compilazione (make o cmake --build .) in una cartella di build separata dai sorgenti; le librerie di soli template (header-only) si dichiarano INTERFACE perché non producono codice precompilato.CMake - build di progetti C++ →, STL - container associativi (map e set)I container associativi memorizzano gli elementi in base a una chiave: map è un contenitore ordinato di coppie (chiave, valore) ottimizzato per cercare dalla chiave (albero bilanciato, ricerca in tempo logaritmico), con operator[] che crea l'elemento se la chiave manca (il valore è inizializzato con il costruttore di default, quindi ++words[s] conta le occorrenze); gli elementi sono pair<const Chiave, Valore> con campi first e second. set è una map senza valori: contiene solo chiavi ordinate, senza [] né push_back, si usa con insert ed erase, e per i tipi definiti dall'utente richiede una funzione d'ordine (un function object). Le varianti: multimap e multiset ammettono chiavi ripetute; le versioni unordered_* usano tabelle hash e non mantengono l'ordine. Header . - Decisioni:
set_time= intervallo da aggiungere; analizza i transiti in ; velocità di tratta = km tra i varchi / ore tra i passaggi, multa se ; veicoli al minuto = transiti / minuti trascorsi; velocità media = media delle misure; sanzionati = targhe distinte; targhe uniche; uscita sempre dopo l'ingresso. - Classi:
Highway(legge, valida, numera),Simulator(percorsi, passaggi, scrittura file),Tutor(analisi, statistiche, comandi), structMarker,Segment,Run,Passage; duemain. Nessuna ereditarietà (sarebbe eccessiva). - Strutture dati: passaggi in
vectorordinato per tempo con indicenext_(ogni passaggio visto una volta);unordered_map<targa, Last>per l'ultimo varco;unordered_set<targa>per i sanzionati;vector<long>per i conteggi; nessunanew/delete. - Tempo di passaggio: a distanza dall'ingresso, intervalli di km e s; nell'intervallo giusto si aggiungono s. Esempio
RO388TO: s. Esempio velocitàEX876BJ: 20 km in s = km/h.
#include "tutor.h"
#include <algorithm>
#include <iomanip>
#include <istream>
#include <ostream>
#include <sstream>
#include <utility>
bool read_passages(std::istream& in, int gate_count, std::vector<Passage>& out, std::string& error)
{
std::vector<Passage> result;
std::string line;
int line_no = 0;
while (std::getline(in, line)) {
++line_no;
if (line.find_first_not_of(" \t\r") == std::string::npos) continue;
std::istringstream fields{line};
Passage p;
if (!(fields >> p.gate >> p.plate >> p.time) || p.gate < 1 || p.gate > gate_count || p.time < 0) {
error = "riga " + std::to_string(line_no) + " di Passages non valida";
return false;
}
result.push_back(p);
}
out = result;
return true;
}
Tutor::Tutor(const Highway& highway, std::vector<Passage> passages)
: highway_{highway}, passages_{std::move(passages)}, next_{0}, now_{0.0},
last_{}, gate_count_(highway.gates().size(), 0), speed_sum_{0.0}, speed_n_{0}, sanctioned_{}
{
std::stable_sort(passages_.begin(), passages_.end(),
[](const Passage& a, const Passage& b) { return a.time < b.time; });
}
void Tutor::reset()
{
next_ = 0;
now_ = 0.0;
last_.clear();
std::fill(gate_count_.begin(), gate_count_.end(), 0L);
speed_sum_ = 0.0;
speed_n_ = 0;
sanctioned_.clear();
}
// Un passaggio sotto il varco p.gate: se il veicolo e' gia' passato sotto un varco precedente,
// la velocita' media sulla tratta e' (km tra i due varchi) / (tempo impiegato).
void Tutor::process(const Passage& p, std::ostream& out)
{
++gate_count_[static_cast<std::size_t>(p.gate - 1)];
auto it = last_.find(p.plate);
if (it != last_.end() && p.time > it->second.time) {
double km = highway_.gate_km(p.gate) - highway_.gate_km(it->second.gate);
double speed = km / ((p.time - it->second.time) / 3600.0);
speed_sum_ += speed;
++speed_n_;
if (speed > kSpeedLimit) {
sanctioned_.insert(p.plate);
out << "SANZIONE " << p.plate << ": tratta varco " << it->second.gate << " -> varco " << p.gate
<< ", " << std::fixed << std::setprecision(1) << speed << " km/h, passaggi a "
<< std::setprecision(3) << it->second.time << " s e " << p.time << " s\n";
}
}
last_[p.plate] = Last{p.gate, p.time}; // il veicolo ora e' sotto questo varco
}
void Tutor::set_time(double delta_seconds, std::ostream& out)
{
now_ += delta_seconds;
long analyzed = 0;
while (next_ < passages_.size() && passages_[next_].time <= now_) { // i transiti in (vecchio, nuovo]
process(passages_[next_], out);
++next_;
++analyzed;
}
out << "istante " << std::fixed << std::setprecision(1) << now_ << " s: " << analyzed << " transiti analizzati\n";
}
void Tutor::stats(std::ostream& out) const
{
out << "istante " << std::fixed << std::setprecision(1) << now_ << " s\n";
for (std::size_t g = 0; g < gate_count_.size(); ++g) {
double per_minute = now_ > 0 ? gate_count_[g] / (now_ / 60.0) : 0.0;
out << "varco " << g + 1 << ": " << gate_count_[g] << " veicoli, " << std::setprecision(2) << per_minute
<< " al minuto\n";
}
out << "velocita' media: " << std::setprecision(1) << (speed_n_ ? speed_sum_ / static_cast<double>(speed_n_) : 0.0)
<< " km/h (" << speed_n_ << " misure)\n";
out << "veicoli sanzionati: " << sanctioned_.size() << '\n';
}
bool Tutor::execute(const std::string& line, std::ostream& out)
{
std::istringstream in{line};
std::string cmd;
if (!(in >> cmd)) return true; // riga vuota
if (cmd == "quit") return false;
if (cmd == "reset") { reset(); out << "sistema azzerato\n"; return true; }
if (cmd == "stats") { stats(out); return true; }
if (cmd == "set_time") {
std::string arg;
in >> arg;
double factor = 1.0; // numero puro = secondi, con 'm' finale = minuti
if (!arg.empty() && arg.back() == 'm') { factor = 60.0; arg.pop_back(); }
std::istringstream number{arg};
double value;
if (arg.empty() || !(number >> value) || !number.eof() || value <= 0) {
out << "errore: set_time richiede un intervallo positivo (es. 20 oppure 20m)\n";
return true;
}
set_time(value * factor, out);
return true;
}
out << "errore: comando sconosciuto '" << cmd << "' (set_time, reset, stats, quit)\n";
return true;
}Prova: 10000 veicoli (seme 2026), 20107 passaggi; dopo
set_time 2000m6290 righe (6289 sanzioni più il riepilogo), 4151 veicoli sanzionati, velocità media 139,7 km/h su 10107 misure; somma dei conteggi dei varchi = 20107. Autostrada non valida o file mancante: messaggio ed uscita con codice 1.Controllo consegna:
CMakeLists.txtunico,-O2,Data/,README.txt, nessun eseguibile, autore in ogni file, argomenti controllati, nessun magic number. Errori tipici: ereditarietà inutile, ricerca lineare per targa, allocazione manuale, argomenti non controllati.Codice (tutor/include/tutor.h):
#ifndef TUTOR_H
#define TUTOR_H
#include <iosfwd>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "highway.h"
#include "simulator.h"
// Legge Passages.txt ("<# varco> <targa> <istante>" per riga); false + messaggio se una riga non e' valida.
bool read_passages(std::istream& in, int gate_count, std::vector<Passage>& out, std::string& error);
// Il sistema tutor: scorre i passaggi in ordine di tempo, calcola la velocita' media di ogni veicolo
// su ogni tratta tra due varchi consecutivi e segnala chi supera il limite.
class Tutor {
public:
static constexpr double kSpeedLimit = 130.0; // km/h
// 'passages' puo' essere in qualsiasi ordine: il costruttore lo ordina per tempo.
Tutor(const Highway& highway, std::vector<Passage> passages);
void set_time(double delta_seconds, std::ostream& out); // avanza e analizza i transiti nell'intervallo
void reset(); // torna all'istante 0, statistiche azzerate
void stats(std::ostream& out) const;
// Esegue una riga di comando ("set_time 20m", "reset", "stats"). false se il comando e' "quit".
bool execute(const std::string& line, std::ostream& out);
double now() const { return now_; }
private:
struct Last { // ultimo varco visto per un veicolo
int gate;
double time;
};
void process(const Passage& p, std::ostream& out);
const Highway& highway_;
std::vector<Passage> passages_; // ordinati per tempo
std::size_t next_; // primo passaggio non ancora analizzato
double now_; // istante corrente (secondi)
std::unordered_map<std::string, Last> last_; // targa -> ultimo varco: accesso in O(1)
std::vector<long> gate_count_; // veicoli transitati, per varco (indice = numero - 1)
double speed_sum_; // somma delle velocita' medie misurate
long speed_n_; // numero di misure
std::unordered_set<std::string> sanctioned_; // targhe sanzionate (senza duplicati)
};
#endif // TUTOR_H- Codice (tutor/src/simulator.cpp):
#include "simulator.h"
#include <algorithm>
#include <iomanip>
#include <ostream>
#include <unordered_set>
Simulator::Simulator(const Highway& highway, unsigned seed, SimulatorConfig config)
: highway_{highway}, rng_{seed}, config_{config} {}
// mt19937 e' definito dallo standard bit per bit: a parita' di seme dà la stessa sequenza su ogni
// compilatore (le distribuzioni di <random>, invece, no). Il resto usato qui ha una distorsione
// trascurabile (intervalli di poche decine di valori su 2^32).
int Simulator::rand_int(int lo, int hi)
{
return lo + static_cast<int>(rng_() % static_cast<unsigned long>(hi - lo + 1));
}
double Simulator::rand_real(double lo, double hi)
{
return lo + (hi - lo) * (static_cast<double>(rng_()) / 4294967296.0);
}
std::string Simulator::random_plate()
{
std::string p;
for (int i = 0; i < 2; ++i) p += static_cast<char>('A' + rand_int(0, 25));
for (int i = 0; i < 3; ++i) p += static_cast<char>('0' + rand_int(0, 9));
for (int i = 0; i < 2; ++i) p += static_cast<char>('A' + rand_int(0, 25));
return p;
}
std::vector<Run> Simulator::generate_runs(int count)
{
std::vector<Run> runs;
runs.reserve(static_cast<std::size_t>(count));
std::unordered_set<std::string> used; // le targhe devono essere tutte diverse
const int junctions = static_cast<int>(highway_.junctions().size());
double t = 0.0;
for (int i = 0; i < count; ++i) {
t += rand_real(config_.min_gap, config_.max_gap); // ingresso del veicolo successivo
Run run;
do { run.plate = random_plate(); } while (!used.insert(run.plate).second);
run.entry = rand_int(1, junctions - 1); // non l'ultimo: da li' non si puo' uscire
run.exit = rand_int(run.entry + 1, junctions); // uscita piu' avanti dell'ingresso
run.start = t;
double distance = highway_.junction_km(run.exit) - highway_.junction_km(run.entry);
double covered = 0.0;
while (covered < distance) { // intervalli fino a coprire la distanza
Segment s{rand_int(config_.min_speed, config_.max_speed), rand_int(config_.min_minutes, config_.max_minutes)};
run.profile.push_back(s);
covered += s.speed_kmh * s.minutes / 60.0;
}
runs.push_back(run);
}
return runs;
}
// Secondi (dall'ingresso) necessari per arrivare a km_from_entry km dallo svincolo di ingresso.
double Simulator::seconds_to_reach(const Run& run, double km_from_entry) const
{
double elapsed = 0.0, remaining = km_from_entry;
for (const Segment& s : run.profile) {
double length = s.speed_kmh * s.minutes / 60.0; // km percorsi in questo intervallo
if (remaining <= length) return elapsed + remaining / s.speed_kmh * 3600.0;
remaining -= length;
elapsed += s.minutes * 60.0;
}
return elapsed; // non succede: il profilo copre la distanza
}
std::vector<Passage> Simulator::compute_passages(const std::vector<Run>& runs) const
{
std::vector<Passage> passages;
for (const Run& run : runs) {
double from = highway_.junction_km(run.entry);
double to = highway_.junction_km(run.exit);
for (const Marker& gate : highway_.gates())
if (gate.km > from && gate.km < to) // i varchi incontrati nel tragitto
passages.push_back(Passage{gate.id, run.plate, run.start + seconds_to_reach(run, gate.km - from)});
}
std::sort(passages.begin(), passages.end(), [](const Passage& a, const Passage& b) {
if (a.time != b.time) return a.time < b.time;
return a.gate < b.gate;
});
return passages;
}
void Simulator::write_runs(std::ostream& os, const std::vector<Run>& runs)
{
os << std::fixed << std::setprecision(3);
for (const Run& r : runs) {
os << r.plate << ' ' << r.entry << ' ' << r.exit << ' ' << r.start;
for (std::size_t i = 0; i < r.profile.size(); ++i)
os << (i == 0 ? " " : ", ") << r.profile[i].speed_kmh << ' ' << r.profile[i].minutes;
os << '\n';
}
}
void Simulator::write_passages(std::ostream& os, const std::vector<Passage>& passages)
{
os << std::fixed << std::setprecision(3);
for (const Passage& p : passages) os << p.gate << ' ' << p.plate << ' ' << p.time << '\n';
}