Added README.md

This commit is contained in:
samuele-brusegan
2026-05-14 18:14:23 +02:00
parent d731d21783
commit 0490f081ed

300
README.md Normal file
View File

@@ -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 56 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
}
<<Runnable>> 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<Node>` 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 <ref_snippet file="/home/samuele/IdeaProjects/brusegan_samuele_VETF04000T_secondaprova/src/main/java/org/example/models/MainLogic.java" lines="46-83" />):
```
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<Node>`: 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 <ref_snippet file="/home/samuele/IdeaProjects/brusegan_samuele_VETF04000T_secondaprova/src/main/java/org/example/models/MainLogic.java" lines="51-58" /> e <ref_snippet file="/home/samuele/IdeaProjects/brusegan_samuele_VETF04000T_secondaprova/src/main/java/org/example/models/MainLogic.java" lines="76-79" />.
- 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 `<release>25</release>` 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):
```
<nome>,<prestigio>,<tipo>
```
- `<nome>`: stringa libera (no virgole)
- `<prestigio>`: numero (parsato come `double`)
- `<tipo>`: `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 |