Esercizio 13misure da file e dizionario di misure
In questa pagina 3
Testo (appello del 18 giugno 2018, "Misure", e appello del 30 giugno 2021, "Misure memorizzate in un dizionario", di Fondamenti di Informatica, Ingegneria dell'Informazione UniPD; adattato da un tema d'esame in Java, qui in Python; i due testi sono fusi perché usano lo stesso dizionario).
Parte A (giugno 2018). Una misura è una coppia tempo/valore: tempo è un intero, valore un reale. Scrivere:
- la classe
Misuracon costruttoreMisura(t, v),tempo(),valore(), una rappresentazione testuale nel formato(tempo valore)(per esempio(1 27.5)); l'ordine naturale è determinato dal tempo; - la classe
Segnale, un dizionariotempo → valore(interfaccia:make_empty,is_empty,size,insert(t, v)che sovrascrive se esiste già una misura di tempot,find(t)che lancia un'eccezione se non c'è,remove(t)che termina silenziosamente se non c'è), il cui costruttore legge le misure da un file in cui ciascun campione è nel formato(t v); il metodoto_sorted_array()restituisce le misure ordinate secondo il loro ordine naturale.
Con il file temperatura.txt
(5 25.5) (2 25.7) (4 25.4)
(2 25.2) (3 25.3) (1 25.1)l'uscita attesa è: size = 5, poi le misure (1 25.1) ... (5 25.5) in ordine, poi di nuovo le cinque misure (stampate con find e poi rimosse), infine size = 0.
Parte B (giugno 2021). Un programma di prova legge da file i dati delle coppie, una coppia per riga, numeri in virgola mobile (file campioni.txt qui sotto), nel dizionario:
- se una riga non contiene almeno due numeri, invia a standard error il messaggio
riga n: <tipo dell'errore>connnumero della riga; - se uno dei due numeri non è in formato corretto, invia a standard error
riga n: <tipo dell'errore>; - ogni coppia valida viene inserita e si stampa a standard output
--> chiave/valore; - poi si ricavano le chiavi ordinate e si rimuovono tutte le coppie stampando
<-- chiave/valore.
1.25 42.0
1,25 36.0
1.25
1.0 30.0
0.75 24.0
0.5 18.0
0.25 42.0Teoria: File di record e controllo degli errori riga per rigaSchema per leggere un file (o lo standard input) di record, uno per riga: formati con separatore, controllo di ogni riga, messaggi di errore con il numero di riga su standard error, righe da saltare, fine dell'input su riga vuota; versione in Python con split, int, float e eccezioni; versione in C con fgets, sscanf e strtol.File di record e controllo degli errori riga per riga →, Classi ed ereditarietà in PythonAttributi di istanza e di classe, metodi di istanza, di classe e statici; confronto e ordinamento con eq e lt; proprietà; ereditarietà, super(), override e polimorfismo; classi astratte come interfacce; eccezioni personalizzate; overloading e shadowing in Python.Classi ed ereditarietà in Python →, Dizionari e insiemi in PythonADT mappa e insieme; dict con chiavi hashable, accesso, get, iterazione, conteggi e raggruppamenti; set e operazioni insiemistiche; tabelle hash e costo O(1) medio; Counter e defaultdict.Dizionari e insiemi in Python →, Eccezioni in PythonEccezioni e traceback, eccezioni predefinite più comuni, try/except/else/finally, raise per segnalare errori, propagazione lungo le chiamate.Eccezioni in Python →.
Parte A
Misuratiene_te_v. L'ordine naturale (quello usato dasorted,min,max) si definisce con__lt__; l'uguaglianza__eq__confronta il tempo, coerente con l'ordine, efunctools.total_orderingricava gli altri confronti.__repr__dà(t v).Segnalesi appoggia a undict(tempo → valore):insertsovrascrive per costruzione (d[t] = v),removecond.pop(t, None)non fa nulla se la chiave manca,findsollevaKeyError. (Nel tema Java il dizionario andava realizzato con un array: vedi Realizzare contenitori su array e listeCome si realizza un ADT contenitore partendo da un array: lunghezza logica e capacità con raddoppio (costo ammortizzato), dizionario su array ordinato con ricerca binaria, coda doppia su array circolare, coda con priorità a livelli, ADT costruiti sopra altri ADT (pila di code, pila reversibile); tabella dei costi.Realizzare contenitori su array e liste →.)- Lettura. Ogni campione ha la forma
(t v)e su una riga ce ne sono tre. Non si può spezzare consplit()(il blank sta dentro le parentesi): si usa un'espressione regolare che estrae le coppie,\(\s*(\S+)\s+(\S+)\s*\), e si convertono i due campi (intper il tempo,floatper il valore). I campioni con lo stesso tempo si sovrascrivono:(2 25.7)è sostituito da(2 25.2)che viene dopo nel file, quindi i tempi distinti sono 5.
import io
import re
from functools import total_ordering
@total_ordering
class Misura:
"""Coppia tempo/valore; l'ordine naturale è per tempo."""
def __init__(self, t, v):
self._t, self._v = t, v
def tempo(self):
return self._t
def valore(self):
return self._v
def __eq__(self, altra):
return self._t == altra._t
def __lt__(self, altra):
return self._t < altra._t
def __repr__(self):
return f"({self._t} {self._v})"
class Segnale:
"""Dizionario tempo -> valore."""
def __init__(self, f=None):
self._d = {}
if f is not None:
for t, v in re.findall(r"\(\s*(\S+)\s+(\S+)\s*\)", f.read()):
self.insert(int(t), float(v))
def make_empty(self):
self._d.clear()
def is_empty(self):
return not self._d
def size(self):
return len(self._d)
def insert(self, t, v): # sovrascrive se il tempo c'è già
self._d[t] = v
def find(self, t):
if t not in self._d:
raise KeyError(t) # il testo chiede un'eccezione
return self._d[t]
def remove(self, t):
self._d.pop(t, None) # silenziosa se assente
def to_sorted_array(self):
return sorted(Misura(t, v) for t, v in self._d.items())
temperatura = "(5 25.5) (2 25.7) (4 25.4)\n(2 25.2) (3 25.3) (1 25.1)"
s = Segnale(io.StringIO(temperatura))
print("*** size =", s.size(), "***") # 5
c = s.to_sorted_array()
print("*** insert ***")
for m in c:
print(m) # (1 25.1) ... (5 25.5)
print("*** find e remove ***")
for m in c:
print(Misura(m.tempo(), s.find(m.tempo()))) # stesso elenco
s.remove(m.tempo())
print("*** size =", s.size(), "***") # 0Uscita: size = 5, (1 25.1), (2 25.2), (3 25.3), (4 25.4), (5 25.5) (due volte) e size = 0, come richiesto.
Parte B
Per ogni riga si estraggono i campi con split() (qui la riga è una coppia semplice) e si distinguono due errori:
- meno di due campi → si solleva
IndexError(in JavaNoSuchElementExceptiondiScanner.next()); - un campo che non è un numero (
"1,25": la virgola decimale non esiste in Python) →floatsollevaValueError(in JavaNumberFormatException).
Un solo try con due except (o un except (IndexError, ValueError)) segnala l'errore e prosegue con la riga successiva invece di fermare il programma; il numero di riga si ottiene con enumerate(f, start=1). I messaggi di errore vanno a standard error, quelli di avanzamento a standard output.
import sys
def prova(f, out, err):
s = Segnale()
print("*** LETTURA DATI DA FILE E INSERIMENTO ELEMENTI NEL DIZIONARIO ***", file=out)
for n, riga in enumerate(f, start=1):
try:
campi = riga.split()
if len(campi) < 2:
raise IndexError("meno di due numeri")
chiave, valore = float(campi[0]), float(campi[1])
s.insert(chiave, valore)
print(f"--> {chiave}/{valore}", file=out)
except (IndexError, ValueError) as e:
print(f"riga {n}: {type(e).__name__}", file=err)
print(file=out)
print("*** RIMOZIONE ELEMENTI DAL DIZIONARIO ***", file=out)
for k in sorted(s._d): # chiavi ordinate
v = s.find(k)
s.remove(k)
print(f"<-- {k}/{v}", file=out)
campioni = "1.25 42.0\n1,25 36.0\n1.25\n1.0 30.0\n0.75 24.0\n0.5 18.0\n0.25 42.0"
out, err = io.StringIO(), io.StringIO()
prova(io.StringIO(campioni), out, err) # con il file vero: prova(open(sys.argv[1]), sys.stdout, sys.stderr)
print(out.getvalue())
print("stderr:", err.getvalue())Uscita su standard output:
*** LETTURA DATI DA FILE E INSERIMENTO ELEMENTI NEL DIZIONARIO ***
--> 1.25/42.0
--> 1.0/30.0
--> 0.75/24.0
--> 0.5/18.0
--> 0.25/42.0
*** RIMOZIONE ELEMENTI DAL DIZIONARIO ***
<-- 0.25/42.0
<-- 0.5/18.0
<-- 0.75/24.0
<-- 1.0/30.0
<-- 1.25/42.0e su standard error riga 2: ValueError (la riga 1,25 36.0 ha un numero malformato) e riga 3: IndexError (la riga 1.25 ha un solo numero). Le righe 2 e 3 non interrompono il programma.
sorted(s._d) ordina le chiavi: in un dizionario con dict l'ordine di iterazione è quello di inserimento, quindi l'ordinamento va chiesto esplicitamente (in Java toSortedKeyArray).
Errori comuni
- Spezzare
(t v)consplit(): i campi risulterebbero(5e25.5). insertche non sovrascrive e crea duplicati;removeche solleva un'eccezione quando il tempo manca (il testo la vuole silenziosa).- Un
tryche avvolge l'intero ciclo: la prima riga errata esce dal ciclo e le altre vengono perse. - Scrivere gli errori su standard output; contare le righe da 0.
- Convertire il tempo con
float: i tempi sono interi (2e2.0sono la stessa chiave in undict, ma il formato di stampa cambierebbe).
Versione ripasso
Appelli del 18 giugno 2018 e del 30 giugno 2021 (UniPD, in Java; adattato a Python). Teoria: File di record e controllo degli errori riga per rigaSchema per leggere un file (o lo standard input) di record, uno per riga: formati con separatore, controllo di ogni riga, messaggi di errore con il numero di riga su standard error, righe da saltare, fine dell'input su riga vuota; versione in Python con split, int, float e eccezioni; versione in C con fgets, sscanf e strtol.File di record e controllo degli errori riga per riga →, Classi ed ereditarietà in PythonAttributi di istanza e di classe, metodi di istanza, di classe e statici; confronto e ordinamento con eq e lt; proprietà; ereditarietà, super(), override e polimorfismo; classi astratte come interfacce; eccezioni personalizzate; overloading e shadowing in Python.Classi ed ereditarietà in Python →, Dizionari e insiemi in PythonADT mappa e insieme; dict con chiavi hashable, accesso, get, iterazione, conteggi e raggruppamenti; set e operazioni insiemistiche; tabelle hash e costo O(1) medio; Counter e defaultdict.Dizionari e insiemi in Python →.
A. Misura(t, v): ordine naturale per tempo (__lt__, __eq__ + @total_ordering), __repr__ → (t v). Segnale = dizionario tempo → valore: insert sovrascrive (d[t] = v), find solleva KeyError, remove silenziosa (d.pop(t, None)), to_sorted_array = sorted(Misura(...)). Lettura dei campioni (t v) con re.findall(r"\(\s*(\S+)\s+(\S+)\s*\)", testo) (split() rompe le parentesi); con temperatura.txt: size = 5 (il tempo 2 è sovrascritto: (2 25.2)), poi size = 0 dopo le rimozioni.
B. Per ogni riga (enumerate(f, start=1)): campi = riga.split(); meno di 2 campi → IndexError; float(...) su "1,25" → ValueError; except → print(f"riga {n}: {type(e).__name__}", file=err) e si prosegue. Coppie valide: --> k/v. Poi for k in sorted(s._d): find, remove, <-- k/v.
Sul file di esempio: stderr riga 2: ValueError, riga 3: IndexError; inserimenti 1.25, 1.0, 0.75, 0.5, 0.25; rimozioni in ordine crescente.
Errori comuni: split() su (t v); insert con duplicati; remove che solleva; try attorno a tutto il ciclo; errori su stdout; tempi convertiti con float.