Salta al contenuto
Note per Studenti Esercizio 13 · misure da file e dizionario di misure

Esercizio 13misure da file e dizionario di misure

Esame
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 Misura con costruttore Misura(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 dizionario tempo → valore (interfaccia: make_empty, is_empty, size, insert(t, v) che sovrascrive se esiste già una misura di tempo t, 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 metodo to_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> con n numero 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.0

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 →, 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

python
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(), "***")               # 0

Uscita: 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 Java NoSuchElementException di Scanner.next());
  • un campo che non è un numero ("1,25": la virgola decimale non esiste in Python) → float solleva ValueError (in Java NumberFormatException).

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.

python
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.0

e 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) con split(): i campi risulterebbero (5 e 25.5).
  • insert che non sovrascrive e crea duplicati; remove che solleva un'eccezione quando il tempo manca (il testo la vuole silenziosa).
  • Un try che 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 (2 e 2.0 sono la stessa chiave in un dict, 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.

Teoria collegata