Progetto StringCalculator
- Descrizione del progetto
- Esempio di sessione
- Demo
- Suggerimenti per la realizzazione
- Modalità e tempi di consegna
Descrizione del progetto
Questo progetto consiste nella realizzazione di un semplice calcolatore interattivo orientato alle stringhe, chiamato StringCalculator. L'utente interagisce con lo StringCalculator introducendo (da standard input) dei comandi, i quali producono (su standard output) degli effetti: la sintassi dei comandi e l'effetto da essi prodotto è descritto nel seguito.
Si tenga presente che questo documento descrive esclusivamente le caratteristiche minimali che lo StringCalculator deve presentare: siete liberi di (e incoraggiati a) introdurre funzionalità addizionali di cui, ovviamente, si terrà conto in sede di valutazione.
Sintassi generale dei comandi
Ogni comando è costituito da una sequenza di token, separati l'uno dall'altro da (almeno uno) spazio, e terminati da un a-capo. Il primo di questi token, chiamato istruzione, determina il comando da eseguire.
Sono consentiti i seguenti tipi di token:
- parole chiave: sono sequenze di caratteri alfabetici che vengono usate per indicare le istruzioni; le parole chiave sono: def, print, assign, copy, op e quit;
- nomi di variabili: sono sequenze di caratteri alfanumerici (a...z, A...Z, 0...9) che devono iniziare con una lettera (a...z, A...Z);
- costanti numeriche: sono sequenze di cifre (0...9) eventualmente precedute da un segno -;
- costanti stringa: sono sequenze di caratteri alfanumerici precedute da un #; ad esempio, #mamma rappresenta una costante stringa.
Variabili: istruzione def
Il calcolatore manipola delle variabili. Ogni variabile ha un nome e un tipo; esistono solo tre tipi di variabili: numeriche (il cui contenuto è un numero intero), stringa (il cui contenuto è una stringa alfanumerica) e insieme (il cui contenuto è un insieme di stringhe).
Ogni variabile, prima di essere utilizzata, deve essere definita. La definizione della variabile avviene attraverso l'istruzione def, la cui sintassi è:
def <nome variabile> <tipo>dove <tipo> può essere: num, str oppure set (indicanti rispettivamente una variabile numerica, stringa oppure insieme).
Il tentativo di usare (in una delle istruzioni sotto descritte) una variabile non ancora dichiarata deve produrre un errore. Anche il tentativo di definire (con def) una variabile già definita deve produrre un errore.
Una variabile, all'atto della sua definizione, ha il seguente contenuto:
- 0 se si tratta di una variabile numerica;
- la stringa vuota se si tratta di una variabile stringa;
- l'insieme vuoto se si tratta di una variabile insieme.
L'istruzione def non prevede alcun output.
Istruzione print
L'istruzione print stampa il contenuto di una variabile; la sua sintassi è:
print <nome variabile>
Se la variabile è una variabile numerica, verrà stampato il numero in essa contenuto; se è una variabile stringa, la stringa in essa contenuta; se è un insieme, la stampa avrà il seguente formato:
{<stringa>, ..., <stringa>}dove fra le parentesi graffe, separate da virgole, compaiono le stringhe contenute nell'insieme, senza ripetizioni, in un ordine arbitrario.
Assegnamenti di costanti e copie
L'istruzione assign serve per assegnare un valore costante a una variabile numerica o stringa. La sua sintassi è:
assign <nome variabile> <valore costante>
Il valore costante deve essere del tipo corretto (una costante numerica se la variabile è di tipo numerico, una costante stringa se la variabile è di tipo stringa): in caso contrario, si produrrà un errore. Si produrrà altresì un errore se si tenta di usare quest'istruzione con una variabile di tipo insieme.
L'istruzione copy serve per copiare il contenuto di una variabile in un'altra variabile. La sua sintassi è:
copy <nome variabile sorgente> <nome variabile destinazione>
L'istruzione ha successo se le due variabili sono dello stesso tipo. Inoltre, ha successo nei seguenti casi:
- se la variabile sorgente è di tipo numerico e la variabile destinazione è di tipo stringa (in questo caso il valore assegnato sarà la stringa corrispondente al numero);
- se la variabile sorgente è di tipo stringa, ma il suo contenuto è una sequenza non vuota di cifre, eventualmente preceduta da un -, e la variabile destinazione è di tipo numerico (in questo caso il valore contenuto nella stringa sarà convertito in un numero).
Operazioni
L'istruzione op consente di assegnare a una variabile (destinazione) un valore ottenuto applicando un qualche operatore ai contenuti di due variabili (chiamate operandi). Le variabili coinvolte nell'operazione possono non essere distinte.
La sintassi dell'istruzione è:
op <operatore> <nome var. operando 1> [<nome var. operando 2>] <nome var. destinazione>Notate che l'operando 2 può essere opzionale.
Alcuni degli operatori disponibili prevedono che tutte le variabili coinvolte siano dello stesso tipo, e hanno un significato diverso a seconda del tipo delle variabili coinvolte nell'operazione. Questi operatori (che richiedono tutti due operandi) sono:
Operatore | su var. numeriche | su var. stringa | su var. insieme |
---|---|---|---|
+ | somma | concatenazione | unione |
- | differenza | massimo prefisso comune | differenza insiemistica |
* | prodotto | interleaving (vedi sotto) | concatenazione insiemistica (vedi sotto) |
/ | divisione intera | massimo suffisso comune | intersezione |
L'interleaving di due stringhe si definisce come la stringa ottenuta alternando i caratteri della prima ai caratteri della seconda; ad esempio: cane*pesce=cpaersncee, torquato*tasso=ttoarsqsuoato, ma*davvero=mdaavvero.
La concatenazione insiemistica di due insiemi di stringhe è l'insieme delle stringhe ottenibili concatenando una stringa del primo insieme con una stringa del secondo; ad esempio: {con,pre,in}/{testo,torto}={contesto,pretesto,intesto,contorto,pretorto,intorto}.
Altri operatori disponibili sono consentiti solo per certi tipi di variabili:
Operatore | tipo operando 1 (x) | tipo operando 2 (y) | tipo risultato | contenuto risultato |
---|---|---|---|---|
! | stringa | insieme | insieme | y unione {x} |
^ | stringa | numerico | stringa | xxx...x (y volte) |
& | insieme | - | numerico | cardinalità di x |
& | stringa | - | numerico | lunghezza di x |
% | stringa | stringa | insieme | shuffle fra x e y (vedi sotto) |
Lo shuffle fra due stringhe è l'insieme delle stringhe ottenibili alternando in qualunque modo possibile le lettere delle due stringhe. Una definizione ricorsiva di questa operazione è la seguente:
w%ε={w} w%cv=Unione{ {w'c}*(w''%v), per ogni w',w'' tali che w=w'w''}dove w e v sono stringhe, c è un carattere e ε indica la parola vuota.
Istruzione quit
L'istruzione quit termina l'esecuzione dello StringCalculator.
Esempio di sessione
Quello che segue è un esempio di sessione d'uso dello StringCalculatore: il simbolo $ rappresenta il prompt che indica che il calcolatore è pronto a ricevere un comando. Le righe che non iniziano con il prompt sono gli output emessi dallo StringCalculator.
$ def a str $ def b str $ def c str $ def ai num $ def bi num $ def as set $ assign a #mamma $ assign b #marmo $ assign c #asermo $ def d str $ op + a b d $ print d mammamarmo $ op - a b d $ print d ma $ op * a b d $ print d mmaamrmmao $ op / a b d $ print d $ op / a c d $ print d $ op / b c d $ print d rmo $ op & a ai $ print ai 5 $ assign bi 3 $ print bi 3 $ def ci num $ op + ai bi ci $ print ci 8 $ op * ai bi ci $ print ci 15 $ op - ai bi ci $ print ci 2 $ op / ai bi ci $ print ci 1 $ op ! a as as $ op ! b as as $ print as {mamma,marmo} $ def bs set $ op ! c bs bs $ print bs {asermo} $ def cs set $ op + as bs cs $ print cs {mamma,asermo,marmo} $ op - as bs cs $ print cs {mamma,marmo} $ op - as as cs $ print cs {} $ op * as bs cs $ print cs {mammaasermo,marmoasermo} $ op / as bs cs $ print cs {} $ op ^ a ai c $ print c mammamammamammamammamamma $ op & cs bi $ print bi 0 $ op & as bi $ print bi 2 $ print a mamma $ print b marmo $ assign a #ciao $assign b #treno $ op % a b as $ print as {trecinoao,tcriaoeno,tcireaono,ctriaeono,tcreniaoo,ctreniaoo,ctriaenoo,ctrienaoo,trecinaoo, ctiraeono,tcrienoao,ciatroeno,treciaono,tcireanoo,ctrienoao,ctireanoo,trencoiao,ctrieaono, ctiaroeno,tciaoreno,trciaeono,trencioao,tcreianoo,ctreiaono,trceianoo,trceiaono,ciatreono, citreaono,trecnoiao,trcienaoo,trenciaoo,tciraenoo,tcrieaono,trcenoiao,ciatoreno,tciraeono, trecianoo,citraenoo,ctrieanoo,trecniaoo,citraoeno,trcieanoo,ctiarenoo,ctireaono,ctriaoeno, ctreianoo,trciaoeno,trcieaono,tcreiaono,citrenaoo,ctreinoao,citaroeno,tcreinoao,tcirenaoo, citaoreno,tciaroeno,tcriaenoo,ctiraenoo,citreanoo,ctiraoeno,trciaenoo,ctreinaoo,ciaotreno, trceniaoo,tcirenoao,trecnioao,ctrenioao,ciatrenoo,ctirenaoo,tciarenoo,ctiareono,tcriaeono, tcreinaoo,tciraoeno,ctrenoiao,tciareono,citarenoo,trcienoao,tcrenoiao,citrenoao,trceinoao, ctiaoreno,tcrieanoo,tcrenioao,citareono,trceinaoo,trenociao,ctirenoao,citraeono,tcrienaoo,trcenioao} $ quit
Demo
Potete scaricare un demo di soluzione di questo problema. Se volete sperimentare con questo demo, salvate il file .jar ed eseguitelo con il comando:
java -cp stringcalculator.jar it.unimi.mat.proj.stringcalculator.StringCalculator
Nota bene: se il file prog.jar non si trova nelle directory di libreria della JVM, dovrete metterlo nella directory corrente e scrivere
java -cp stringcalculator.jar:prog.jar it.unimi.mat.proj.stringcalculator.StringCalculator
Suggerimenti per la realizzazione
In questa sezione, vogliamo dare alcuni consigli su una possibile realizzazione del progetto. È evidente che si tratta solo di una delle possibili soluzioni del problema, e non è detto né che si tratti della migliore né che sia necessario in alcun senso seguire queste indicazioni.
In primo luogo, due classi di utilità che potrebbero rivelarsi preziose per risolvere il problema (oltre a quelle viste durante il corso) sono java.util.StringTokenizer e la gerarchia java.util.Set. In particolare:
- uno StringTokenizer serve per spezzare una stringa in token sulla base di un insieme di separatori; uno string tokenizer si crea passandogli una stringa (da spezzare) e una stringa di separatori (p.es., lo spazio); una volta creato, invocando su di esso il metodo nextToken() si ottengono in sequenza i token da cui la stringa è costituita, e il metodo hasMoreTokens() indica se ci siano altri token oppure no;
- un Set è un insieme, ed ha metodi per ottenerne la cardinalità, per unirgli un altro insieme (addAll(...)), per calcolare l'intersezione (retainAll(...)) ecc.; poiché Set è solo un'interfaccia, un insieme si istanzia usando una delle implementazioni disponibili (p.es. HashSet; attenzione: se volete usare un HashSet per memorizzarvi istanze di classi implementate da voi [cosa che non vi dovrebbe capitare, visto che dovrete gestire solo insiemi di stringhe], dovete leggere con cura il contratto mutuo dei metodi equals e hashCode nella classe Object per essere sicuri di fornirne un'implementazione coerente).
Una possibile soluzione, consiste in primo luogo nel realizzare una classe astratta Variable le cui istanze rappresentano delle variabili. La classe astratta contiene un attributo che rappresenta il nome (unica caratteristica comune a tutte le variabili) e alcuni metodi di utilità: per esempio, il metodo equals (due variabili sono considerate uguali sse hanno lo stesso nome) e un metodo toString(). La classe contiene alcuni metodi astratti: per esempio, un metodo void assign(String x) che assegna alla variabile un valore costante descritto dalla stringa x, oppure un metodo void copy(Variable v) che copia il valore di v nella variabile (dopo eventuali conversioni). Altro metodo presente sarà Object op(Variable v, char operator) che "opera" questa variabile con v, usando l'operatore specificato, e restituisce il valore da assegnare alla variabile destinazione. Tutti questi metodi devono poter sollevare eccezioni (ad esempio, se l'operatore non esiste, o se i tipi delle variabili coinvolti non sono corretti ecc.).
La classe Variable ha tre sottoclassi concrete, NumericVariable, StringVariable e SetVariable per i tre tipi di variabili. Ciascuna di queste classi avrà un attributo per contenere il valore della variabile (si tratterà di un attributo di tipo int, String e Set, rispettivamente).
Una classe eseguibile provvede a leggere i comandi, a interpretarli e a invocare gli opportuni metodi. Questa classe gestisce una sequenza contenente le variabili fino a questo punto dichiarate (istanze di Variable). Ricordate che Sequenza ha un metodo find che, dato un oggetto, ne cerca uno uguale nella sequenza, e lo restituisce; quindi, per cercare una variabile con nome dato, basta che ne creiate una (fittizia) con il nome che vi interessa, e invochiate il metodo find.
Modalità e tempi di consegna
- Validità. Questo progetto viene pubblicato il 12/01/2005, ed è valido fino al 01/05/2005.
- Gruppi. Il progetto può essere realizzato da singoli, o da gruppi composti da non più di tre persone.
- Consegna. I gruppi che intendono sostenere l'esame, devono procedere
come segue:
- inviare entro il 20/01/2005 una mail al docente con subject Iscrizione progetto di programmazione (nomegruppo), dove nomegruppo è un nome scelto dal gruppo; la mail deve contenere l'elenco dei nomi, cognomi, numeri di matricola e indirizzi di email dei componenti del gruppo; la mail deve inoltre contenere un'indicazione del periodo in cui i singoli componenti del gruppo intendono sostenere l'esame: componenti diversi possono sostenere l'esame in periodi diversi, ma sarebbe preferibile nei limiti del possibile che ciò non avvenisse; per chi intende sostenere l'esame nel mese di febbraio, si legga la successiva nota al riguardo;
- il docente proporrà una data di consegna del progetto;
- entro la data di consegna, il gruppo deve inviare al docente una mail con
subject Progetto di programmazione (nomegruppo); la mail deve contenere in
allegato:
- un file .zip o .tar contenente il sorgente delle classi realizzate (i file .java);
- un file .zip o .tar contenente la documentazione del programma; il formato di tale documentazione è libero, ma si incoraggiano gli studenti a non usare formati proprietari;
- il docente valuterà il progetto, tenendo conto fra l'altro dei seguenti parametri:
- correttezza rispetto alle specifiche
- adeguatezza e completezza della documentazione
- eventuali funzionalità aggiuntive
- buona strutturazione del codice
- correttezza nell'uso della struttura di ereditarietà
- se la valutazione del progetto è sufficiente, il docente concorda con i componenti del gruppo la data (o le date) dell'orale; a fronte di un orale insufficiente, verrà concordata la data di una nuova prova orale;
- Appello di febbraio. Visto che presumibilmente molti studenti
intenderanno sostenere l'appello di febbraio, per evitare di dover fissare appuntamenti diversi, si
stabiliscono fin d'ora le seguenti date (valide per tutti i gruppi in cui almeno un componente
intenda sostenere l'esame orale entro febbraio):
- data di consegna: 04/02/2005
- valutazione dei progetti: entro il 14/02/2005
- orali di febbraio: 16/02/2005