Salta al contenuto
Note per Studenti Esercizio - Sistema tutor autostradale (progetto finale 2025-26)

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 viv_i per un tempo tit_i; viv_i e tit_i sono interi casuali in [80,190][80, 190] km/h e [5,15][5, 15] 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 - container associativi (map e set) →, STL - algoritmi, predicati e lambdaLa STL ha circa 80 algoritmi generici (header e ) che lavorano su sequenze [first, last) di qualunque contenitore tramite iteratori: find/find_if, count/count_if, sort, copy/copy_if/unique_copy, merge, equal_range, equal, accumulate, inner_product. find cerca un valore e restituisce l'iteratore alla prima occorrenza o end(); find_if verifica un predicato (funzione che restituisce bool) passato come puntatore a funzione, function object (classe con operator()) o lambda expression. Una variabile globale per passare la soglia al predicato è da evitare; il function object la memorizza in un dato membro. Una lambda cattura -> tipo { corpo } definisce la funzione sul posto e può catturare le variabili locali per valore ([=], [x]) o per riferimento ([&], [&x]). sort(b, e) ordina con < e sort(b, e, cmp) con un criterio, solo su iteratori random access; copy richiede spazio a destinazione.STL - algoritmi, predicati e lambda →, STL - sequenze, iteratori e container (vector, list, string)La STL (Standard Template Library) fornisce contenitori (vector, list, map, ...) e algoritmi generici (sort, find, accumulate, ...) tenuti insieme da una interfaccia unificata: la sequenza, un intervallo [begin, end) di elementi gestito tramite iteratori (oggetti che identificano un elemento: * dereferenzia, ++ avanza, == e != confrontano; end è il primo elemento dopo l'ultimo, quindi una sequenza vuota ha begin == end). Gli algoritmi conoscono solo gli iteratori e i contenitori espongono solo iteratori, così i due mondi sono disaccoppiati e cambiare contenitore non cambia l'algoritmo. Un iteratore di lista è una classe annidata con un puntatore al nodo; auto deduce il tipo dell'iteratore. vector (contiguo, [] e at(), insert/erase costosi e che invalidano gli iteratori), string (un vector di char con + e +=), list (nodi concatenati, insert/erase senza spostare gli altri elementi, niente []) e char[] (array C: dimensione fissa, niente iteratori, == confronta i puntatori) a confronto.STL - sequenze, iteratori e container (vector, list, string) →.


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 (tvecchio,tnuovo](t_{\text{vecchio}}, t_{\text{nuovo}}]
formato di Runs.txt <v_i t_i> sono coppie: targa ingresso uscita partenza v0 t0, v1 t1, ...
velocità media di una tratta km tra i due varchitempo tra i due passaggi in ore\dfrac{\text{km tra i due varchi}}{\text{tempo tra i due passaggi in ore}}; 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 264×10326^4 \times 10^3 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 (O(n)O(n) in tutto, ordinamento iniziale O(nlog⁡n)O(n \log n))
ultimo varco di un veicolo unordered_map<string, Last> accesso per targa in tempo medio costante; la map (albero) costerebbe O(log⁡n)O(\log n) 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 ee al tempo ss e ha il profilo (v1,t1),(v2,t2),…(v_1, t_1), (v_2, t_2), \ldots raggiunge il km dd (misurato dallo svincolo di ingresso) così: si percorrono gli intervalli uno dopo l'altro; l'intervallo ii copre ℓi=viti/60\ell_i = v_i t_i / 60 km (con tit_i in minuti) e dura 60ti60 t_i secondi; nel primo intervallo in cui la distanza residua è ≤ℓi\le \ell_i si aggiunge residuavi⋅3600\dfrac{\text{residua}}{v_i} \cdot 3600 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 d=15d = 15 km e il passaggio avviene a 6,858+15156⋅3600=6,858+346,154=353,0126{,}858 + \frac{15}{156} \cdot 3600 = 6{,}858 + 346{,}154 = 353{,}012 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 132⋅5/60=11132 \cdot 5/60 = 11 km in 300 s; restano 14 km a 92 km/h, cioè 1492⋅3600=547,826\frac{14}{92} \cdot 3600 = 547{,}826 s; totale 2,584+300+547,826=850,4102{,}584 + 300 + 547{,}826 = 850{,}410 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: 20387,097/3600=186,0\frac{20}{387{,}097/3600} = 186{,}0 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.cpp

CMakeLists.txt (uno solo, nella radice; -O2; due librerie e due eseguibili, più la prova integrata):

CMakeLists.txtcmake
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

tutor/include/highway.hcpp
#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
tutor/src/highway.cppcpp
#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

tutor/include/simulator.hcpp
#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
tutor/src/simulator.cppcpp
#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::mt19937 con seme esplicito (il seme predefinito è 2026): la sequenza di mt19937 è fissata dallo standard, quindi a parità di seme Runs.txt è identico su ogni compilatore. Gli interi si ottengono con rng_() % intervallo (distorsione dell'ordine di 10−810^{-8}, trascurabile) e i reali con rng_() / 2^32; le distribuzioni di <random> si evitano perché le implementazioni differiscono tra librerie.
  • Targhe uniche con un unordered_set e un ciclo do ... while (!used.insert(...).second).
  • Ingresso e uscita: l'ingresso è scelto tra gli svincoli 1..J−11..J-1 (JJ = numero di svincoli), l'uscita tra e+1..Je+1..J: l'uscita è sempre dopo, e la scelta non può restare senza candidati.
  • Profilo: si aggiungono intervalli finché la distanza coperta ∑viti/60\sum v_i t_i / 60 raggiunge la distanza tra gli svincoli; l'ultimo intervallo resta intero nel file anche se il veicolo esce prima della sua fine (seconds_to_reach si ferma alla distanza richiesta).
  • Passaggi: per ogni veicolo i varchi con from < km < to; poi un solo sort globale 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

tutor/include/tutor.hcpp
#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
tutor/src/tutor.cppcpp
#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:

  • process aggiorna 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à in last_. 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_time consuma i passaggi con time <= now_ a partire da next_: ogni passaggio è analizzato una sola volta anche se set_time viene chiamato mille volte; reset riporta next_ a 0 senza ricaricare né riordinare.
  • execute separa l'interfaccia testuale dalla logica (set_time(double), stats, reset si possono chiamare direttamente, come fa la prova integrata). Il suffisso m si gestisce togliendo l'ultimo carattere; il numero si legge con istringstream (non con stod, che lancia eccezioni) e si controlla number.eof() per rifiutare 20x.
  • I messaggi di errore dei comandi non terminano il programma: il tutor resta in ascolto.

I due eseguibili

tutor/src/main_simulator.cppcpp
// 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;
}
tutor/src/main_tutor.cppcpp
// 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):

tutor/Data/Highway.txt
50 V
0 S
35 S
100 V
12 S
5 V
75 V
85 S
60 S
25 V
110 S

Prova integrata senza file

Per provare simulatore e tutor insieme senza file (e con uscita ripetibile) c'è un terzo programma che usa solo stringstream:

tutor/src/test_integrato.cppcpp
// 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: 4151

Controlli di coerenza sull'uscita: dopo aver analizzato tutto, la somma dei conteggi dei varchi è 2042+3602+4689+5137+4637=201072042 + 3602 + 4689 + 5137 + 4637 = 20107, esattamente il numero di righe di Passages.txt; le misure di velocità sono 10107=20107−1000010107 = 20107 - 10000: 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 analizzati

Con 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.txt unico nella radice, -O2, Data/Highway.txt, estratti di Runs.txt e Passages.txt (tagliati se grandi), README.txt con le decisioni della tabella iniziale e i problemi non risolti, nessun eseguibile; verifica finale scaricando la consegna in un'altra cartella e rifacendo cmake .., 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, main in file a sé.
  • Errori tipici del corso: ereditarietà inutile; vector ricercato 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 nel main.

Versione ripasso

tutor/src/tutor.cppcpp
#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 2000m 6290 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.txt unico, -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):

tutor/include/tutor.hcpp
#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):
tutor/src/simulator.cppcpp
#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';
}

Teoria collegata