From 0490f081ed7613a783af56abe58257ffdb41d88c Mon Sep 17 00:00:00 2001 From: samuele-brusegan Date: Thu, 14 May 2026 18:14:23 +0200 Subject: [PATCH] Added README.md --- README.md | 300 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 300 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..3f9ba56 --- /dev/null +++ b/README.md @@ -0,0 +1,300 @@ +# brusegan_samuele_VETF04000T_secondaprova + +Elaborato per la **Gara Nazionale di Informatica (GNI) 2026** — ITIS Pininfarina, Moncalieri (TO). + +- Autore: **Brusegan Samuele** +- Codice meccanografico: `VETF04000T` +- Prova: **Seconda prova** + +--- + +## Indice + +1. [Traccia](#traccia) +2. [Modello del problema](#modello-del-problema) +3. [Algoritmo](#algoritmo) +4. [Struttura del progetto](#struttura-del-progetto) +5. [Requisiti](#requisiti) +6. [Build & Run](#build--run) +7. [Dataset](#dataset) + +--- + +## Traccia + +Il testo ufficiale della prova è un PDF di 5–6 pagine fornito dalla commissione GNI 2026 e **non è ancora incluso in questo repository**. Verrà aggiunto in `docs/traccia.pdf` appena disponibile. + +### Sintesi inferita dal codice + +Dato un insieme di **Punti di Interesse (POI)**, ciascuno caratterizzato da: + +- un **nome**, +- un valore di **prestigio** (numero reale), +- un **tipo** (`0` = culturale, `1` = panoramico — convenzione inferita dal dataset), + +va costruito un **itinerario turistico** che: + +1. parte dall'ultimo POI del dataset, il **Castello di Moncalieri** (nodo fisso di partenza); +2. visita un sottoinsieme degli altri POI in un certo ordine; +3. massimizza il **prestigio totale** accumulato lungo il percorso. + +Sul singolo passaggio fra due POI consecutivi viene applicato un **bonus di varietà**: se i due POI sono di tipo diverso (uno culturale e uno panoramico), il prestigio del POI di arrivo viene moltiplicato per un coefficiente `MULTIPLIER = 1.5`. Il primo POI del percorso contribuisce con il proprio prestigio "nudo". + +Nel codice attuale sono fissati anche: + +- `K = 4` — numero di nodi che vengono lasciati "fuori" dall'esplorazione ricorsiva sul fondo dello stack (vedi [Algoritmo](#algoritmo)); +- partenza dal `Castello di Moncalieri`, che viene rimosso dalla lista e ri-aggiunto in coda a tutti i path generati. + +> Quando avrò a disposizione il PDF della traccia, questa sezione verrà sostituita con il testo esatto e i vincoli ufficiali. + +--- + +## Modello del problema + +Il problema è modellabile come **ricerca della permutazione di massimo "prestigio"** su un insieme di nodi etichettati, con una funzione di costo che dipende dall'**ordine** (per via del bonus di tipo) — è quindi una variante di problema NP-hard sullo spazio delle permutazioni, risolto qui per via esatta sfruttando la dimensione contenuta dell'istanza (≤ 20 POI nei dataset forniti). + +### Classi del dominio + +- **`Node`** (`org.example.models.Node`) + POI immutabile. Campi: `name : String`, `prestigio : double`, `type : boolean` (true ↔ "panoramico"). Implementa `equals`/`hashCode` su tutti i campi: è quindi usabile come chiave/elemento di `HashSet` / `HashMap`, sfruttato per la memoization. + +- **`Path`** (`org.example.models.Path`) + Sequenza ordinata di `Node` con il proprio `prestigioTotale` cumulato. Espone: + - `add(Node)` — append in coda, aggiorna il prestigio in O(1) usando l'ultimo nodo come "predecessore"; + - `addAsFirstNode(Node)` — insert in testa, **ricalcola** il prestigio scorrendo tutto il path (perché cambia il vicino sinistro di ciò che prima era in testa) — O(n); + - `getPrestigio()` — getter del prestigio cumulato; + - copy-constructor `Path(Path other)` — usato per copie difensive nella cache. + +- **`MainLogic`** (`org.example.models.MainLogic`) + Orchestratore: importa i dati, lancia la ricorsione, sceglie il path migliore. Espone anche `needBonus(Node s, Node e)` come predicato del bonus di varietà. + +- **`Importer`** (`org.example.models.Importer`) + Lettore CSV "manuale" (split su `,`). Path hard-coded su `./src/main/resources/punti_interesse.csv`. + +### Diagramma classi + +```mermaid +classDiagram + class Node { + -String name + -double prestigio + -boolean type + +Node(String, double, boolean) + +Node(String, double, int) + +getType() boolean + +getPrestigio() double + +equals(Object) boolean + +hashCode() int + +toString() String + } + + class Path { + -ArrayList~Node~ nodes + -double prestigioTotale + +Path() + +Path(Path other) + +add(Node) void + +addAsFirstNode(Node) void + +getPrestigio() double + -calculateDeltaPrestigio(Node, Node) double + -updatePrestigio() void + } + + class MainLogic { + +double MULTIPLIER$ + +int K$ + +double LENGHT$ + -HashMap~Set~Node~,List~Path~~ cache$ + +main(String[])$ void + +importData()$ ArrayList~Node~ + +getPaths(List~Node~)$ ArrayList~Path~ + +needBonus(Node, Node)$ boolean + } + + class Importer { + -String FILENAME_CSV_DATA$ + +importData()$ ArrayList~Node~ + } + + class PathBuilder { + +run() void + } + <> PathBuilder + + class App { + +start(Stage) void + +main(String[])$ void + } + + class MainUI { + +start(Stage)$ void + } + + class Controller + + Path "1" o-- "*" Node : contiene + MainLogic ..> Importer : usa + MainLogic ..> Path : crea/elabora + MainLogic ..> Node : usa + Importer ..> Node : istanzia + App --> MainUI : avvia + MainUI ..> Controller : (previsto) + Controller ..> MainLogic : (previsto) + PathBuilder ..> MainLogic : (previsto) +``` + +--- + +## Algoritmo + +### Idea + +Enumerazione delle permutazioni della lista dei POI con due ottimizzazioni: + +1. **Memoization** su sotto-istanze: due rami di ricorsione che arrivano allo stesso *insieme* (non sequenza!) di nodi rimanenti producono lo stesso insieme di sotto-path. La chiave della cache è quindi un `Set` e non una lista ordinata. +2. **Pruning sul fondo**: la ricorsione si ferma quando restano `K` nodi (con `K = 4`), restituendo un path vuoto. I `K` nodi residui non vengono permutati a quel livello — la logica completa di chi gestisce gli ultimi `K` nodi è parte dei [TODO](#todo) (vedi anche [Note di correttezza](#note-di-correttezza)). + +Pseudocodice (vedi ): + +``` +getPaths(nl): + key = set(nl) + if key in cache: + return deep_copy(cache[key]) + + if |nl| == LENGHT - K - 1: + return [ Path() ] # caso base + + result = [] + for n in nl: + sub = getPaths(nl \ {n}) # ricorsione su lista priva di n + for p in sub: p.addAsFirstNode(n) # n diventa la testa + result += sub + + cache[key] = deep_copy(result) # copia difensiva + return result +``` + +`main()` poi: + +``` +nl = importData() +moncalieri = nl.removeLast() +paths = getPaths(nl) +for p in paths: p.add(moncalieri) # Moncalieri in coda a tutti i path +best = argmax(paths, key = p.getPrestigio()) +``` + +### Funzione obiettivo + +Per un path `p = [n₁, n₂, …, nₖ]`: + +``` +prestigio(p) = prestigio(n₁) + + Σ_{i=2..k} ( needBonus(n_{i-1}, n_i) + ? MULTIPLIER · prestigio(n_i) + : prestigio(n_i) ) +``` + +dove `needBonus(a, b) ↔ a.type ≠ b.type` e `MULTIPLIER = 1.5`. + +### Complessità + +- Senza memoization: `O(n!)` in tempo, `O(n)` in spazio di stack. +- Con memoization su `Set`: gli stati distinti sono `2^n`, ma per ciascuno serve costruire `O(n)` path → spazio `O(n · 2^n)`, tempo dominato da costruzione e copia delle liste. Con `n ≤ ~20` resta tranquillamente gestibile. +- `addAsFirstNode` è `O(n)` per via dell'aggiornamento del prestigio: il costo per path è quindi `O(n²)`. + +### Note di correttezza + +- La cache **deve** restituire copie difensive perché i chiamanti mutano i path tramite `addAsFirstNode` / `add`. Vedi e . +- Il caso base `|nl| == LENGHT - K - 1` taglia gli ultimi `K` livelli di ricorsione restituendo path vuoti: occorre verificare che questo sia il comportamento voluto dalla traccia (probabilmente la traccia richiede esattamente un percorso lungo `K+1` nodi, partenza inclusa). Da chiarire/refinire una volta letto il PDF. + +--- + +## Struttura del progetto + +``` +. +├── pom.xml +├── nbactions.xml +├── src/ +│ └── main/ +│ ├── java/ +│ │ ├── module-info.java +│ │ └── org/example/ +│ │ ├── App.java # entry point JavaFX +│ │ ├── SystemInfo.java +│ │ ├── controller/ +│ │ │ └── Controller.java # stub +│ │ ├── models/ +│ │ │ ├── Importer.java # parser CSV +│ │ │ ├── Node.java +│ │ │ ├── Path.java +│ │ │ ├── PathBuilder.java # stub Runnable +│ │ │ └── MainLogic.java # ricorsione + cache + main CLI +│ │ └── views/ +│ │ └── MainUI.java # stub UI +│ └── resources/ +│ ├── punti_interesse.csv # dataset principale (14 POI) +│ └── punti_interesse_b.csv # dataset secondario (20 POI) +└── target/ +``` + +--- + +## Requisiti + +- **JDK 25** (vedi `25` nel `pom.xml`) +- **Maven 3.9+** (incluso wrapper `.mvn/`) +- **JavaFX 21.0.2** — risolto da Maven via `org.openjfx:javafx-controls` + +--- + +## Build & Run + +### Compilazione + +```bash +./mvnw clean compile +``` + +### Esecuzione della logica (CLI) + +Per ora il `main` di `MainLogic` stampa direttamente il miglior path su stdout: + +```bash +./mvnw exec:java -Dexec.mainClass="org.example.models.MainLogic" +``` + +oppure, da IDE, eseguire la classe `org.example.models.MainLogic`. + +### Esecuzione dell'app JavaFX (stub) + +```bash +./mvnw javafx:run +``` + +Apre una finestra placeholder. + +> Nota: l'`Importer` usa un path **relativo** (`./src/main/resources/punti_interesse.csv`), quindi va lanciato dalla root del progetto. + +--- + +## Dataset + +Due file CSV in `src/main/resources/`, stesso schema (senza header): + +``` +,, +``` + +- ``: stringa libera (no virgole) +- ``: numero (parsato come `double`) +- ``: `0` (culturale) o `1` (panoramico) + +L'**ultima riga** è sempre il punto di partenza (`Castello di Moncalieri`). + +| File | Righe | +|---|---| +| `punti_interesse.csv` | 14 | +| `punti_interesse_b.csv` | 20 |