Questa pagina descrive il progetto per il corso di Informatica alla Laurea in Matematica per le sessioni dell'anno accademico 2014/2015.
Dopo l'assedio di Troia, Odisseo si accinge a ritornare ad Itaca.
A causa dell'ira di Poseidone, il suo viaggio è tormentato e
Odisseo si ritrova a vagare tra molti luoghi diversi.
Da ogni luogo parte un certo numero di rotte verso altri luoghi
(o anche verso lo stesso luogo).
Ogni rotta ha una direzione precisa: per esempio,
la rotta da A verso B può essere soltanto percorsa in quel verso.
Inoltre, ogni rotta ha un nume tutelare che protegge chi la segue.
Non ci sono vincoli su quale nume sia associato ad una rotta: per
esempio è possibile che da A parta una rotta per B tutelata da X,
una rotta per C tutelata da Y, e una rotta per D tutelata di nuovo da X.
Odisseo però viene aiutato da Hermes, che gli rivela un'importante
informazione: per potere tornare a Itaca, Odisseo deve seguire
una qualunque sequenza di rotte i cui rispettivi numi tutelari
formano una precisa sequenza.
Riuscirà l'eroe multiforme a ritornare ad Itaca?
I luoghi raggiungibili da Odisseo sono rappresentati da numeri naturali
(che il tipo int
può rappresentare).
La città di Troia, dove parte Odisseo è il luogo numero 0,
mentre Itaca, l'arrivo, è il luogo numero 1.
I numi sono rappresentati da caratteri (tipo char
),
e di conseguenza la sequenza di numi rivelata da Hermes è
una String
.
I dati relativi alle rotte e al messaggio di Hermes sono memorizzati su un file di testo nel formato seguente:
N partenza1 nume1 arrivo1 partenza2 nume2 arrivo2 ... partenzaN numeN arrivoN numeAnumeB...numeZ
dove partenza1,...
e arrivo1,...
sono
naturali, mentre numeX
sono singoli caratteri.
La prima riga del file contiene il numero di rotte N
.
Poi, seguono esattamente N
righe con le triple
partenza nume arrivo
che rappresentano le rotte: qui
partenza
e nume
sono separati da un carattere
spazio, così come nume
e arrivo
.
L'ultima riga del file invece contiene il messaggio di Hermes,
e può essere lunga zero o più caratteri (ed è terminata con
un a-capo '\n'
).
Nessun nume è rappresentato col carattere spazio (' '
)
o a-capo ('\n'
).
Le rotte mostrate sopra possono essere rappresentate in un file come segue, assieme al messaggio di Hermes:
8 0 a 2 2 a 0 2 c 1 2 a 2 2 b 3 3 c 1 3 c 2 3 c 4 aaabcbc
in questo caso Odisseo può raggiungere Itaca con il percorso
0-a-2-a-2-a-2-b-3-c-2-b-3-c-1
.
Se il messaggio di Hermes fosse invece stato
aaabcb
, Odisseo non avrebbe potuto raggiungere Itaca
rispettando la sequenza di numi.
Analogamente non avrebbe potuto con aaabbcc
.
Altri esempi:
Si chiede di scrivere una funzione in Java che, dato un nome di file
contenente i dati relativi al problema, restituisca un
boolean
. Il valore true
indica che Odisseo
può salvarsi, mentre false
indica che non può.
Si vedano più sotto i requisiti tecnici che tale funzione deve
soddisfare.
Non è richiesto l'uso di uno specifico algoritmo (fermi restando i requisiti di cui sotto).
Come suggerimento verso una possibile soluzione si fa notare che, nell'esempio sopra:
a
),
Odisseo può raggiungere un qualunque luogo tra: 2aa
),
Odisseo può raggiungere un qualunque luogo tra: 0,2aaa
),
Odisseo può raggiungere un qualunque luogo tra: 0,2aaab
),
Odisseo può raggiungere un qualunque luogo tra: 3aaabc
),
Odisseo può raggiungere un qualunque luogo tra: 1,2,4aaabcb
),
Odisseo può raggiungere un qualunque luogo tra: 3aaabcbc
),
Odisseo può raggiungere un qualunque luogo tra: 1,2,4Quindi, siccome Itaca (1) appare tra le opzioni finali, Odisseo può raggiungerla.
La sequenza di numi di Hermes va seguita interamente. Non importa se Itaca viene raggiunta prima della fine.
La sequenza di numi può essere vuota (ed in tal caso, Odisseo non raggiunge Itaca).
Da un luogo (incluso Troia (0)) possono partire zero rotte.
Ad un luogo (incluso Itaca (1)) possono condurre zero rotte.
Da un luogo possono partire più rotte tutelate dallo stesso nume.
Una rotta può partire ed arrivare nello stesso luogo.
È possibile che Troia e Itaca siano completamente sconnesse, ovvero che nessuna sequenza di rotte le colleghi.
Ci possono essere zero rotte nel file.
Ci possono essere cicli nelle rotte (come nell'esempio sopra), e quindi è possibile passare più volte sullo stesso luogo.
Il progetto verrà valutato secondo i seguenti criteri, che formano un insieme di requisiti sul codice consegnato. Ogni requisito primario è condizione necessaria per il superamento della prova: anche se uno di questi non viene rispettato, la prova non è superata. I requisiti secondari devono essere generalmente rispettati. Nel caso ci siano solo minori violazioni di tali requisiti, la prova è comunque superata. Violazioni gravi possono comunque causare il non superamento della prova.
Correttezza (primario).
Non ci devono essere errori a tempo di compilazione: il programma deve
compilare. Non ci devono essere errori a tempo di esecuzione: il
programma non deve generare eccezioni (per
esempio, DivisionByZero
oppure ArrayIndexOutOfBounds
) quando eseguito su un input
valido. Il programma deve dare l'output desiderato su qualunque
input compatibile con la specifica.
Nota bene:
Nel caso il programma sia scorretto, non è compito di chi corregge
fare il debugging,
ovvero identificare la causa dell'errore e suggerire una modifica
per rimuoverla. Anche quando tale causa fosse nota a chi corregge,
non verrà comunicata allo studente, in quanto sarebbe come suggerire
una parte non banale della soluzione della prova. Infatti, capita
frequentemente che correggere l'errore dopo averlo
individuato diventi banale ("devo usare x
, e non
x+1
"), e che la prova di conseguenza consista prevalentemente
nella ricerca dell'errore.
Corollario:
se provando il programma su un insieme di input campione si riscontrano
già errori di correttezza, chi corregge non è tenuto ad esaminare
il codice del programma.
Invarianti (primario).
Nel "cuore" del programma, dove viene effettivamente
usato l'algoritmo che risolve il problema, si richiede di aggiungere
qualche invariante informale (e non banale). Non è
necessario che tali invarianti siano così precise da essere
direttamente usabili
in una dimostrazione formale di correttezza del codice.
Le invarianti vanno invece aggiunte come documentazione al codice,
per aiutare il lettore a capire come funziona il programma.
Le invarianti vanno aggiunte prima dei cicli while/for
.
Se usate funzioni ricorsive, aggiungete invece una pre- e una post-condizione.
Per esempio:
int num = 10; Punto[] p = ... ; ... // INV: nessuna coppia di punti tra p[0] .. p[num-1] dista più di 4 while (num > 0) { ... }
Requisiti tecnici (primario). L'algoritmo deve essere implementato in una classe fatta esattamente come segue
// File "Progetto.java" // Non usate "package" qui, usate quello di default. // Potete usare import per accedere alle librerie di java. public class Progetto { // con la P maiuscola public static boolean raggiungibile(String nomeFile) { // Scrivete qui la vostra soluzione al problema } // Qui potete aggiungere altri metodi, se volete, incluso // un metodo main. // Il metodo raggiungibile di sopra deve comunque funzionare // anche se viene invocato da un main diverso da quello che scrivete // voi. // Per esempio, il vostro main NON deve inizializzare variabili // che poi vengono usate dentro raggiungibile (inizializzatele // direttamente là). // // Ad esempio, questo main va bene: public static void main(String[] args) { String f = "/home/zunino/java/progetto/test1.txt"; // o altro nome di file nella vostra home System.out.println(raggiungibile(f)); } }
Potete usare anche altre classi in altri file .java
,
oltre a quello di sopra (anche queste classi vanno nel package di default).
La funzione raggiungibile
non deve interagire con l'utente
(chiedere all'utente dati, aprire finestre, ecc). È permesso l'output
di messaggi su console (che verranno ignorati da chi corregge). La funzione
main
, se usata, può interagire con l'utente (ma non sarà
considerata da chi corregge).
Complessità (secondario).
Annotate in un commento vicino alla funzione raggiungibile
la complessità dell'algoritmo che state usando, in funzione del numero
dei luoghi, delle rotte e della lunghezza del messaggio di Hermes
(la sequenza di numi).
Efficienza (secondario). Si faccia attenzione a quali algoritmi si usano e alla loro complessità. Non è richiesto l'uso dell'algoritmo più efficiente possibile per un sottoproblema, ma la soluzione proposta non deve essere enormemente più inefficiente di quanto non si possa fare.
Leggibilità (secondario). Il codice deve essere scritto in modo tale da permettere ad un altro programmatore di comprenderne la logica. Non è sufficiente che il codice "funzioni", o che sia chiaro a chi lo ha scritto. Per aiutare la lettura del codice, potete inserire dei commenti.
Commenti (secondario). Non commentate come state calcolando qualcosa, commentate piuttosto cosa state calcolando. Per esempio, il seguente commento è altamente inutile:
// incremento i i++;
Al contrario, il seguente aiuta a comprendere il codice:
// passo a coordinate polari rho = Math.sqrt(x*x + y*y); theta = Math.atan(y/x);
Per potere partecipare allo scritto di una sessione di esame, il progetto deve essere consegnato entro le date indicate nell'elenco delle sessioni di esame. La consegna si svolge mandando una email avente esattamente le seguenti caratteristiche.
zunino_555555/Progetto.java
(contenente la funzione raggiungibile
)zunino_555555/Rotta.java
zunino_555555/RiemannZetaSolver.java
zunino_555555/GoldbachConjectureCounterexampleGenerator.java
zunino_555555/FastestFourierTrasformInTheWest.java
zunino_555555/FluxCapacitorSimulator.java
zunino_555555/TardisKernel.java
zunino_555555/README.txt
Se avete dubbi sul testo del progetto, ovvero su che cosa vi sta venendo chiesto, chiedete pure delucidazioni per email o a ricevimento. Se invece avete dubbi sulla soluzione dell'esercizio, ovviamente non possiamo suggerirvi nulla.
Il progetto non viene svolto in un ambiente "controllato" come per esempio avviene per un esame scritto, ma vi viene lasciata libertà di svolgerlo dove e quando preferite (compatibilmente con le scadenze). Per esempio, potete usare i laboratori quando liberi, o farlo su un vostro computer personale.
Il progetto è individuale, non di gruppo. Tuttavia, vi è consentito discutere del progetto con altre persone per scambiarsi opinioni a riguardo. Non è consentita, ovviamente, la copia di pezzi di codice inerenti al progetto da uno studente all'altro. Allo stesso modo, farsi fare il progetto da un'altra persona è considerato equivalente a copiare. In caso di dubbi sull'autenticità del progetto, ci riserviamo la possibilità di convocarvi per un colloquio sul codice che avete consegnato, anche dopo l'esame scritto.
Vi è consentito di "copiare" invece pezzi di codice che vi abbiamo fornito noi, o di "tradurre in Java" dei pezzi di codice che potreste trovare su qualche libro o tutorial online. In questo caso, però, dovete obbligatoriamente citare la fonte in un commento nel codice.
Il discutere del progetto su forum di discussione su Internet o simili non è vietato a prescindere, ma è soggetto alle stesse regole della comunicazione tra altri studenti. Inoltre, se iniziate una discussione su un forum riguardo al progetto, dovete obbligatoriamente dichiarare 1) che l'esercizio in questione è un progetto di esame, e 2) che non desiderate che qualcuno vi scriva una soluzione al posto vostro. Se anche chiarendo ciò qualcuno vi risponde includendo del codice, voi non potete includerlo nel progetto.
Se preferite, potete usare anche caratteristiche di Java non viste a lezione (costrutti, librerie, ...), anche se il progetto si può svolgere benissimo anche senza. Dovete però comprendere il codice che state usando: se venite chiamati ad un colloquio (vedi sotto) vi può essere chiesto di spiegarlo.
Potete assumere che l'input da file abbia esattamente il formato di cui sopra. Non vi si chiede di gestire gli errori nell'input in modo controllato: il vostro programma può dare errore o restituire un risultato arbitrario in tal caso. Se fate comunque qualche tipo di validazione sull'input, è cosa bene accetta.
Se volete usare un TreeSet
di char
,
dovete usare TreeSet<Character>
. Il motivo
è che TreeSet
richiede un tipo classe, mentre
char
è un tipo primitivo. Il tipo Character
è un tipo classe che contiene un singolo char
e
viene automaticamente convertito da e in char
da Java.
Allo stesso modo, usate TreeSet<Integer>
per gli
int
.
Siccome dovete leggere il file, Java vi obbliga a gestire un'eccezione
FileNotFoundException
. Fatelo per esempio come segue:
try { ... // codice che legge il file } catch (FileNotFoundException e) { System.out.println("Errore in lettura: " + e); return true; // o false }
Non vi è permesso di cambiare il tipo della funzione
raggiungibile
con throws FileNotFoundException
e fare in modo che ci pensi il chiamante a gestirla. (In un programma
più serio, questa seconda opzione sarebbe quella "giusta", ma per questo
progetto non vi si chiede una gestione precisa degli errori.)
Quando il messaggio di Hermes è vuoto, dopo la riga dell'ultima rotta c'è un a-capo (che finisce la rotta) seguito direttamente da un altro a-capo (che finisce immediatamente il messaggio di Hermes), e dopo il file di testo finisce. Il file di testo non finisce subito dopo il primo a-capo.
Dove sopra scrivo '\n'
per indicare l'a-capo
non indico la sequenza di due caratteri barra-e-n, ma il char
che rappresenta l'a-capo (che in Java viene rappresentato appunto in
tal modo).
Informatica - Teaching - Home
Roberto Zunino, 2015