# ---
# 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 22 - 11 dicembre 2023
# %% [markdown] slideshow={"slide_type": "slide"} jp-MarkdownHeadingCollapsed=true
# # RECAP:
# - rappresentazione di **espressioni algebriche**
# - calcolo
# - semplificazione
# - parsing
# %% [markdown] slideshow={"slide_type": "slide"}
# # Combinazioni e permutazioni
# - **combinazioni** di **k** elementi scelti tra **N** valori (CON ripetizioni)
# - **permutazioni** di **k** elementi scelti tra **N** valori (SENZA ripetizioni)
# %% [markdown]
# ## Combinazioni
# Per ottenere una delle possibili combinazioni
# - per k volte
# - scelgo uno dei N valori e lo aggiungo al risultato
#
# Per ottenerle tutte
# - inizio con le soluzioni per N=1, ciascuna contenente uno degli N valori
# - per k-1 volte
# - aggiungo ciascuno degli N valori a tutte le soluzioni già calcolate
# %%
## Versione iterativa
def combinazioni_iterativa(L : list, k : int) -> list:
risultato = [ [X] for X in L ]
for i in range(1,k): # per k-1 volte
nuovo_risultato = [ [Y]+precedente for precedente in risultato
for Y in L ]
risultato = nuovo_risultato
return risultato
# %%
# esempio
print(combinazioni_iterativa([1, 2, 3], 3))
# %% [markdown]
# Per realizzarlo ricorsivo
# - usiamo k come indice del problema e lo riduciamo di 1 mano a mano (**riduzione**)
# - il **caso base** è k==1
# - in questo caso ciascuna combinazione contiene uno solo degli N valori
# - altrimenti basta aggiungere (**composizione**) a tutte le sottosoluzioni ciascuno degli N valori
#
# **convergenza**: da k>0 a forza di togliere 1 si arriva a 1
# %%
## versione ricorsiva
def combinazioni_ricorsiva(L:list, k:int) -> list:
if k == 1:
return [ [X] for X in L ]
else:
return [ [Y]+sottosoluzione for sottosoluzione in combinazioni_ricorsiva(L,k-1)
for Y in L ]
# %%
# esempio
print(combinazioni_ricorsiva([1, 2, 3], 3))
# %% [markdown]
# ## Permutazioni (k elementi SENZA ripetizioni)
# - idea 1: costruisco tutte le combinazioni e TOLGO quelle con ripetizioni
# - inefficiente (meglio NON GENERARE cose che dovremo filtrare)
# - NON FUNZIONA se nella lista degli elementi da permutare ci sono doppioni
# - idea 2:
# - come la generazione delle combinazioni ma stando attenti a non riusare lo stesso elemento
# %% [markdown]
# ## versione ricorsiva
# - come prima, riduciamo su k
# - ma ogni volta che scegliamo un elemento da aggiungere ad una sottosoluzione NON lo passiamo alla chiamata ricorsiva
# %%
def permutazioni_ricorsiva(L:list, k:int) -> list:
assert k <= len(L), f"k={k} è maggiore della lunghezza di L ({len(L)})"
if k==1:
return [ [X] for X in L]
else:
risultato = []
for i in range(len(L)):
resto = L.copy()
Y = resto.pop(i)
#Y = L[i] # prendo l'elemento Y
#resto = L[:i]+L[i+1:] # tolgo Y da L
for sottosoluzione in permutazioni_ricorsiva(resto, k-1): # NOTA: la lista NON contiene l'elemento Y
risultato.append([Y]+sottosoluzione)
return risultato
# %%
# Esempio
print(permutazioni_ricorsiva([1,2,3,4],2))
# %%
# %%
# %% [markdown] slideshow={"slide_type": "slide"}
# # Rotazione ricorsiva di immagini
# - vediamo che succede se divido una immagine in 4 parti e la ruoto
# %%% Rotazione di una immagine quadrata di lato 2^n ricorsivamente [markdown] slideshow={"slide_type": "fragment"}
#
#
# ```| 1 |``` | **resta com'è**
#
# |
---|
# %%% Rotazione di una immagine quadrata di lato 2^n ricorsivamente [markdown] slideshow={"slide_type": "fragment"}
#
#
# ```
# 0 | 1
# --|--
# 2 | 3
# ```
#
# | diventa ==> |
#
# ```
# 1 | 3
# --|--
# 0 | 2
# ```
#
# |
---|
# %%% Rotazione di una immagine quadrata di lato 2^n ricorsivamente [markdown] slideshow={"slide_type": "slide"}
#
#
# ```
# 0 1 | 2 3
# 4 5 | 6 7
# ----|----
# 8 9 | A B
# C D | E F
# ```
#
# | diventa ==> |
#
# ```
# 3 7 | B F
# 2 6 | A E
# ----|----
# 1 5 | 9 D
# 0 4 | 8 C
# ```
#
# |
---|
# %%% Rotazione di una immagine quadrata di lato 2^n ricorsivamente [markdown] slideshow={"slide_type": "slide"}
# ## Suddivisione e rotazione ricorsiva NxN
# - se N==1: la matrice resta così (**caso base**)
# - se N>1:
# - divido in 4 sottomatrici (**riduzione**)
# - le ruoto di 90° (**chiamata ricorsiva**)
# - le scambio
# - le fondo in una matrice più grande (**composizione**)
# - a forza di dividere per 2 arrivo sempre a 1 (**convergenza**)
# %%% Rotazione di una immagine quadrata di lato 2^n ricorsivamente slideshow={"slide_type": "slide"}
# A B
# C D
def dividiP2(matrice):
"divido la matrice nei suoi 4 quadranti"
# NOTA: la matrice deve avere dimensione potenza di 2
L = len(matrice)
assert L>1 and L%2==0 # FIXME: dovrei controllare 2^n==L
metà = L//2
fascia_superiore = matrice[:metà]
fascia_inferiore = matrice[metà:]
A = [ riga[:metà] for riga in fascia_superiore ]
B = [ riga[metà:] for riga in fascia_superiore ]
C = [ riga[:metà] for riga in fascia_inferiore ]
D = [ riga[metà:] for riga in fascia_inferiore ]
return A, B, C, D
# %% slideshow={"slide_type": "slide"}
# vediamo se funziona
M = [[1,2,3,4],
[5,6,7,8],
[9,10,11,12],
[13,14,15,16]]
N = [[1,2],[3,4]]
print(*dividiP2(M),sep='\n\n')
# %%% Rotazione di una immagine quadrata di lato 2^n ricorsivamente slideshow={"slide_type": "slide"}
# A B
# C D
def fondiP2(A, B, C, D):
"fondo 4 matrici in una sola"
AB = [ rigaA+rigaB for rigaA,rigaB in zip(A,B) ]
CD = [ rigaC+rigaD for rigaC,rigaD in zip(C,D) ]
return AB + CD
# %% slideshow={"slide_type": "slide"}
# test
A = [[1,2],[5,6]]
B = [[3,4],[7,8]]
C = [[9,10],[13,14]]
D = [[11,12],[15,16]]
print(*fondiP2(A,B,C,D), sep='\n')
# %%% Rotazione di una immagine quadrata di lato 2^n ricorsivamente slideshow={"slide_type": "slide"}
# A B
# C D
# B D
# A C
def ruotaP2(M):
"ruoto una matrice che ha lato 2^L"
# se N==1 la ritorno tal quale
if len(M) == 1:
return M
# la divido in 4
A, B, C, D = dividiP2(M)
# le ruoto tutte e 4
Aruotata = ruotaP2(A)
Bruotata = ruotaP2(B)
Cruotata = ruotaP2(C)
Druotata = ruotaP2(D)
# le scambio e le fondo
return fondiP2(Bruotata, Druotata, Aruotata, Cruotata)
# %% slideshow={"slide_type": "slide"}
M = [['1', '2', '3', '4'],
['5', '6', '7', '8'],
['9', 'A', 'B', 'C'],
['D', 'E', 'F', 'G']]
print(*ruotaP2(M), sep='\n')
# %%% Rotazione di una immagine quadrata di lato 2^n ricorsivamente slideshow={"slide_type": "slide"}
def ruota(M):
"generico ruota applicabile a matrici != da potenza di 2"
# allargo la matrice alla prossima potenza di 2 nelle due direzioni
W = len(M[0])
H = len(M)
i = 0
while 2**i < max(W,H):
i +=1
larghezza = altezza = 2**i
# FIXME: lavoiriamo su una copia di M invece che su M
copia = [ riga.copy() for riga in M ]
for riga in copia:
riga += [(0,0,0)] * (larghezza-W)
copia += [ [(0,0,0)] * larghezza for i in range(altezza-H) ]
# ruoto la matrice
ruotata = ruotaP2(copia)
# elimino le righe/colonne aggiunte
ruotata = ruotata[larghezza-W:]
return [ riga[:H] for riga in ruotata ]
# %%% Esempi slideshow={"slide_type": "slide"}
# 3 7 B F
# 2 6 A E
# 1 5 9 D
# 0 4 8 C
M = [ ['0', '1', '2', ],
['4', '5', '6', ],
['8', '9', 'A', ]]
print(*ruota(M), sep='\n')
# %%% Rotazione di immagini slideshow={"slide_type": "slide"}
import images
img = images.load('scarabocchio.png')
ruotata = ruota(img)
images.save(ruotata, 'scarabocchio-90°.png')
images.visd(img), images.visd(ruotata)
# %%% Rotazione di immagini slideshow={"slide_type": "slide"}
# %%%
trecime = images.load('3cime.png')
ruotata = ruota(trecime)
images.save(ruotata, '3cime-ruotata.png')
images.visd(trecime), images.visd(ruotata)
# %% BYE BYE [markdown] slideshow={"slide_type": "slide"}
# # Esercizi d'esame