# ---
# jupyter:
# jupytext:
# formats: ipynb,py:percent
# text_representation:
# extension: .py
# format_name: percent
# format_version: '1.3'
# jupytext_version: 1.15.2
# kernelspec:
# display_name: Python 3 (ipykernel)
# language: python
# name: python3
# ---
# %% [markdown] slideshow={"slide_type": "slide"}
#
# # Fondamenti di Programmazione
#
#
# **Andrea Sterbini**
#
# lezione 8 - 19 ottobre 2023
# %% [markdown] slideshow={"slide_type": "subslide"}
# # RECAP: estrarre i k massimi da N valori generati a caso
# **$O(n)$** volte (per tutti i valori letti)
# aggiorno la lista di **k** migliori elementi
# - tempo $O(k)$ se **non** tengo i k migliori ordinati
# - oppure $O(k*log(k))$ se li tengo ordinati
#
# Sembra inutile tenerli ordinati!
# %% [markdown] slideshow={"slide_type": "subslide"}
# Avevo dato un suggerimento per migliorare
# - trovare in tempo $O(log(k))$ dove inserire X
# - inserire X (purtroppo in tempo $O(k)$)
#
# E questo ci riporta di nuovo a $O(k)$ per aggiornare i $k$ elementi
#
# Ci vorrebbe una struttura dati con inserimento, cancellazione e minimo in $O(log(k))$
# %% [markdown] slideshow={"slide_type": "subslide"}
# ### SOLUZIONE: from sortedcontainers import [SortedList](https://grantjenks.com/docs/sortedcontainers/sortedlist.html)
# - $O(log(k))$ inserimento di un nuovo valore
# - $O(log(k))$ lettura minimo (elemento a indice 0)
# - $O(log(k))$ cancellazione del minimo
#
# Risultato finale: tempo **$O(n*log(k))$**
# %%
from sortedcontainers import SortedList
help(SortedList.add)
help(SortedList.__delitem__) # usato da 'del massimi[0]' per eliminare il minimo
help(SortedList.__getitem__) # usato da 'massimi[0]' per leggere il minimo
# %% slideshow={"slide_type": "subslide"}
from sortedcontainers import SortedList
def aggiorna_k_massimi_SC(massimi, X, k):
if len(massimi) < k:
massimi.add(X) # O(log(k))
elif massimi[0] < X: # O(log(k))
del massimi[0] # O(log(k))
massimi.add(X) # O(log(k))
# quindi il tempo nel caso peggiore è O(log(k))
# %% slideshow={"slide_type": "subslide"}
def k_massimi_SC_crescente(N, k):
massimi = SortedList()
for X in range(N): # il caso peggiore è la sequenza crescente, ripetuto N volte
aggiorna_k_massimi_SC(massimi, X, k) # O(log(k))
return massimi
# quindi il tempo peggiore è O(log(k)*N)
# %% run_control={"marked": true} slideshow={"slide_type": "subslide"}
# %timeit k_massimi_SC_crescente(1_000_000, 30)
# %timeit k_massimi_SC_crescente(1_000_000, 300)
# %timeit k_massimi_SC_crescente(1_000_000, 3000)
# %timeit k_massimi_SC_crescente(1_000_000, 30000)
# %%
from math import log2
[ log2(X) for X in [30, 300, 3000, 30000]]
# %% [markdown]
# # PAUSA
# %% [markdown] slideshow={"slide_type": "slide"}
# # Tipi in Python
#
# Python NON dichiara nè controlla a runtime se i tipi dei dati forniti alle funzioni (e ritornati) sono "giusti"
#
# Questo perchè i tipi sono dinamici e gli oggetti riferiti dalle variabili "sanno" quali metodi possono essere applicati.
#
# Questo vi fornisce ampia libertà con tutta una serie di controlli eseguiti a run-time.
#
# Nei linguaggi compilati dobbiamo indicare i tipi e questo permette di spostare nella fase di compilazione molti controlli e rendere il codice compilato più veloce.
# %% [markdown] slideshow={"slide_type": "slide"}
# Esistono tool per controllare i tipi usati nel programma, i due più comuni sono:
# - **[mypy](https://mypy-lang.org/)** che fa una analisi statica del codice (ovvero SENZA eseguirlo) e deduce e controlla i tipi
# - **[typeguard](https://typeguard.readthedocs.io/en/latest/)** a run-time (eseguendo il codice)
#
# **Aggiungere i tipi** alle definizioni delle funzioni, variabili ed argomenti ci fa catturare molti più errori
#
# Esistono inoltre dei compilatori di Python (ad es. il progetto **[Codon](https://docs.exaloop.io/codon)** oppure **[Cython](https://cython.org/)**) che lo rendono più efficiente e veloce (al livelo del C/C++) se i tipi vengono indicati (per un sottoinsieme del linguaggio).
# %% [markdown] slideshow={"slide_type": "fragment"}
# ## Come si aggiungono i tipi al codice
# ```python
# # definizione del tipo di una variabile nell'assegnamento
# variabile : tipo = valore
# # oppure come commento
# variabile = valore # type : tipo
# ```
# %% [markdown] slideshow={"slide_type": "fragment"}
# ```python
# # definizione del tipo degli argomenti e del risultato di una funzione
# def funzione( argomento1 : tipo1, ...) -> return_type :
# codice della funzione
# # oppure come commento
# def funzione( argomento1, ...):
# # type:(tipo1, ...) -> return_type
# codice della funzione
# ```
#
# Queste "annotazioni" finiscono in un attributo dell'oggetto funzione e possono essere esaminate e controllate con **`mypy`** oppure con **`typeguard`**
# %% [markdown] slideshow={"slide_type": "subslide"}
# ## Come si descrivono i tipi
# - potete usare direttamente i nomi delle classi corrispondenti
# - **bool, int, float, dict, set, tuple, list, ...**
#
# ```python
# pi : float = 3.14
# dati : list[int] = [ 3, 6, 12, 45 ]
# ```
# %% [markdown] slideshow={"slide_type": "subslide"}
# Per i contenitori possiamo indicare
# **tra parentesi quadre** il tipo degli elementi contenuti nel contenitore
#
# esempio | descrizione
# ---:|:---
# **list** | lista eterogenea
# **list[int]** | lista omogenea di **int** (di lunghezza indefinita)
# **dict[str, int]** |dizionario con chiavi tutte **str** e valori tutti **int**
# **set[float]** |insieme omogeneo di **float**
# **tuple[int, float]** |coppia di valori (**int**, **float**) nell'ordine
# **tuple[int, ...]** | tupla di lunghezza variabile contenente solo **int**
# %% [markdown] slideshow={"slide_type": "subslide"}
# ## Potete usare sinonimi (alias) al posto di tipi complessi
# Questo aiuta a rendere più leggibili le annotazioni
# ```python
# # una scheda è un dizionario di coppie 'info':'valore'
# Scheda = dict[str, str]
# # una agendina è una lista di schede
# Agenda = list[Scheda]
# ```
# Si tendono ad usare nomi maiuscoli
# %% [markdown] slideshow={"slide_type": "subslide"}
# ## Ci sono tipi speciali nel modulo `typing`
# **`from typing import ...`**
# - **None** per indicare il valore **None**
# - **Any** per indicare che va bene qualsiasi tipo
# - **NoReturn** per funzioni non tornano valori (lanciano sempre errore)
# - **Union[ T1, T2 ]** oppure **T1 | T2** per indicare che sono validi sia **T1** che **T2**
# %% [markdown] slideshow={"slide_type": "subslide"}
# - **Optional[T]** equivale a **T | None**
# - **Callable[[...], ReturnType]**
per indicare una funzione che riceve gli argomenti **[...]** e torna un **ReturnType**
# - **TypeVar** per definire tipi parametrici
# - ...
# %% slideshow={"slide_type": "subslide"}
## Esempio di annotazioni di tipo
from typing import Sized # i Sized hanno il metodo __len__ e se ne può calcolare la dimensione
# qui ho un criterio di ordinamento che
# - accetta cose che hanno una lunghezza (Sized)
# - e produce coppie (int,Sized)
def criterio(elemento : Sized) -> tuple[int,Sized]:
return len(elemento), elemento
# i tipi sono inseriti nell'attributo __annotations__ dell'oggetto funzione
criterio.__annotations__
# %% slideshow={"slide_type": "subslide"}
# provo a eseguire un test su sorted
def test_sorted():
L : list[Sized] = [ '932', '1', '23', '045', 2 ] # <== ATTENZIONE intero!!!
expected : list[Sized] = ['1', '2', '23', '045', '932']
result = sorted(L,key=criterio)
assert result == expected
if __name__ == '__main__':
test_sorted()
# scopriamo l'errore SOLO A RUNTIME!!!
# %% [markdown] slideshow={"slide_type": "subslide"}
# ## come verificare i tipi staticamente? (senza eseguire il codice)
#
# **`mypy --pretty sorted.py`**
#
# **mypy** scopre l'errore di tipo già dall'assegnamento!!!
# %%
# !mypy --pretty sorted.py
# %% [markdown] slideshow={"slide_type": "subslide"}
# ## MA è anche utile verificare i tipi a runtime
# - possono dipendere da dati esterni
# - **mypy** non è detto che abbia tutte le info necessarie
# - moduli non tipati o tipati male
# - alcune deduzioni ancora non sono implementate
# %% [markdown] slideshow={"slide_type": "subslide"}
# ### Run-time type-checking
# Non si fa così: **`python sorted.py`**
#
# (lo scopriamo all'ultimo momento, eseguendo **len**)
# %%
# !python sorted.py
# %% [markdown] slideshow={"slide_type": "subslide"}
# ### Run-time type-checking
# NON si fa così: **`pytest sorted.py`**
# ```
# E TypeError: object of type 'int' has no len()
# ```
#
# Si è scoperto solo perchè **len(int)** non funziona
# %%
# !pytest sorted.py
# %% [markdown] slideshow={"slide_type": "subslide"}
# ### Molto meglio: col modulo `typeguard`
# Che si installa nell'Anaconda prompt col comendo
#
# **`conda install typeguard -c conda-forge`**
# %% [markdown] slideshow={"slide_type": "subslide"}
# ### Si usa eseguendo in Anaconda prompt il comando
#
# **`pytest sorted.py --typeguard-packages=sorted`**
#
# **`E typeguard.TypeCheckError : argument "elemento" (int) is not an instance of collections.abc.Sized`**
#
# BENE: adesso **typeguard** si è accorto che **2** non è di tipo **Sized** quando ha controllato l'assegnamento
# %%
# !pytest sorted.py --typeguard-packages=sorted
# %% slideshow={"slide_type": "subslide"}
### Oppure (sconsigliato): MODIFICANDO IL CODICE
from typeguard import typechecked
from typing import Sized
@typechecked # decoratore che controlla i tipi in ingresso e uscita
def criterio(el : Sized) -> tuple[int,Sized]:
return len(el), el
LL : list[Sized] = [ 'uno', 2 ]
sorted(LL, key=criterio) # <== viene scovato qui
# %% [markdown] slideshow={"slide_type": "subslide"}
# ## Cosa vi consiglio di fare
# Definite i tipi degli argomenti e i risultati delle vostre funzioni (e le vostre variabili)
#
# Chiamate
#
# **```mypy --pretty programma.py```**
#
# **```pytest programma.py --typeguard-packages=programma```**
# %% [markdown] slideshow={"slide_type": "subslide"}
# ## Come usiamo i tipi negli HW obbligatori
#
# - tutte le funzioni fornite sono annotate coi tipi
# - uno dei test dello HW verifica i tipi a runtime (e che non li togliate)
# %% [markdown]
# # INTERVALLO
# %%
# %%HTML